feat: add setting for notification list style
This commit is contained in:
@@ -16,6 +16,7 @@ import { ReplyProvider } from '@/providers/ReplyProvider'
|
|||||||
import { ScreenSizeProvider } from '@/providers/ScreenSizeProvider'
|
import { ScreenSizeProvider } from '@/providers/ScreenSizeProvider'
|
||||||
import { ThemeProvider } from '@/providers/ThemeProvider'
|
import { ThemeProvider } from '@/providers/ThemeProvider'
|
||||||
import { TranslationServiceProvider } from '@/providers/TranslationServiceProvider'
|
import { TranslationServiceProvider } from '@/providers/TranslationServiceProvider'
|
||||||
|
import { UserPreferencesProvider } from '@/providers/UserPreferencesProvider'
|
||||||
import { UserTrustProvider } from '@/providers/UserTrustProvider'
|
import { UserTrustProvider } from '@/providers/UserTrustProvider'
|
||||||
import { ZapProvider } from '@/providers/ZapProvider'
|
import { ZapProvider } from '@/providers/ZapProvider'
|
||||||
import { PageManager } from './PageManager'
|
import { PageManager } from './PageManager'
|
||||||
@@ -38,8 +39,10 @@ export default function App(): JSX.Element {
|
|||||||
<ReplyProvider>
|
<ReplyProvider>
|
||||||
<MediaUploadServiceProvider>
|
<MediaUploadServiceProvider>
|
||||||
<KindFilterProvider>
|
<KindFilterProvider>
|
||||||
|
<UserPreferencesProvider>
|
||||||
<PageManager />
|
<PageManager />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
</UserPreferencesProvider>
|
||||||
</KindFilterProvider>
|
</KindFilterProvider>
|
||||||
</MediaUploadServiceProvider>
|
</MediaUploadServiceProvider>
|
||||||
</ReplyProvider>
|
</ReplyProvider>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import ParentNotePreview from '@/components/ParentNotePreview'
|
import ParentNotePreview from '@/components/ParentNotePreview'
|
||||||
|
import { NOTIFICATION_LIST_STYLE } from '@/constants'
|
||||||
import { getEmbeddedPubkeys, getParentBech32Id } from '@/lib/event'
|
import { getEmbeddedPubkeys, getParentBech32Id } from '@/lib/event'
|
||||||
import { toNote } from '@/lib/link'
|
import { toNote } from '@/lib/link'
|
||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
|
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
|
||||||
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 { useMemo } from 'react'
|
||||||
@@ -19,6 +21,7 @@ export function MentionNotification({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
const { pubkey } = useNostr()
|
const { pubkey } = useNostr()
|
||||||
|
const { notificationListStyle } = useUserPreferences()
|
||||||
const isMention = useMemo(() => {
|
const isMention = useMemo(() => {
|
||||||
if (!pubkey) return false
|
if (!pubkey) return false
|
||||||
const mentions = getEmbeddedPubkeys(notification)
|
const mentions = getEmbeddedPubkeys(notification)
|
||||||
@@ -42,6 +45,7 @@ export function MentionNotification({
|
|||||||
sentAt={notification.created_at}
|
sentAt={notification.created_at}
|
||||||
targetEvent={notification}
|
targetEvent={notification}
|
||||||
middle={
|
middle={
|
||||||
|
notificationListStyle === NOTIFICATION_LIST_STYLE.DETAILED &&
|
||||||
parentEventId && (
|
parentEventId && (
|
||||||
<ParentNotePreview
|
<ParentNotePreview
|
||||||
eventId={parentEventId}
|
eventId={parentEventId}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import NoteStats from '@/components/NoteStats'
|
|||||||
import { Skeleton } from '@/components/ui/skeleton'
|
import { Skeleton } from '@/components/ui/skeleton'
|
||||||
import UserAvatar from '@/components/UserAvatar'
|
import UserAvatar from '@/components/UserAvatar'
|
||||||
import Username from '@/components/Username'
|
import Username from '@/components/Username'
|
||||||
|
import { NOTIFICATION_LIST_STYLE } from '@/constants'
|
||||||
import { toNote, toProfile } from '@/lib/link'
|
import { toNote, toProfile } from '@/lib/link'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { useNotification } from '@/providers/NotificationProvider'
|
import { useNotification } from '@/providers/NotificationProvider'
|
||||||
|
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
|
||||||
import { NostrEvent } from 'nostr-tools'
|
import { NostrEvent } from 'nostr-tools'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -38,22 +40,52 @@ export default function Notification({
|
|||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
const { pubkey } = useNostr()
|
const { pubkey } = useNostr()
|
||||||
const { isNotificationRead, markNotificationAsRead } = useNotification()
|
const { isNotificationRead, markNotificationAsRead } = useNotification()
|
||||||
|
const { notificationListStyle } = useUserPreferences()
|
||||||
const unread = useMemo(
|
const unread = useMemo(
|
||||||
() => isNew && !isNotificationRead(notificationId),
|
() => isNew && !isNotificationRead(notificationId),
|
||||||
[isNew, isNotificationRead, notificationId]
|
[isNew, isNotificationRead, notificationId]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
const handleClick = () => {
|
||||||
<div
|
|
||||||
className="clickable flex items-start gap-2 cursor-pointer py-2 px-4 border-b"
|
|
||||||
onClick={() => {
|
|
||||||
markNotificationAsRead(notificationId)
|
markNotificationAsRead(notificationId)
|
||||||
if (targetEvent) {
|
if (targetEvent) {
|
||||||
push(toNote(targetEvent.id))
|
push(toNote(targetEvent.id))
|
||||||
} else if (pubkey) {
|
} else if (pubkey) {
|
||||||
push(toProfile(pubkey))
|
push(toProfile(pubkey))
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
|
|
||||||
|
if (notificationListStyle === NOTIFICATION_LIST_STYLE.COMPACT) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-between cursor-pointer py-2 px-4"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<div className="flex gap-2 items-center flex-1 w-0">
|
||||||
|
<UserAvatar userId={sender} size="small" />
|
||||||
|
{icon}
|
||||||
|
{middle}
|
||||||
|
{targetEvent && (
|
||||||
|
<ContentPreview
|
||||||
|
className={cn(
|
||||||
|
'truncate flex-1 w-0',
|
||||||
|
unread ? 'font-semibold' : 'text-muted-foreground'
|
||||||
|
)}
|
||||||
|
event={targetEvent}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground shrink-0">
|
||||||
|
<FormattedTimestamp timestamp={sentAt} short />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="clickable flex items-start gap-2 cursor-pointer py-2 px-4 border-b"
|
||||||
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<div className="flex gap-2 items-center mt-1.5">
|
<div className="flex gap-2 items-center mt-1.5">
|
||||||
{icon}
|
{icon}
|
||||||
@@ -95,6 +127,17 @@ export default function Notification({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function NotificationSkeleton() {
|
export function NotificationSkeleton() {
|
||||||
|
const { notificationListStyle } = useUserPreferences()
|
||||||
|
|
||||||
|
if (notificationListStyle === NOTIFICATION_LIST_STYLE.COMPACT) {
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2 items-center h-11 py-2 px-4">
|
||||||
|
<Skeleton className="w-7 h-7 rounded-full" />
|
||||||
|
<Skeleton className="h-6 flex-1 w-0" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start gap-2 cursor-pointer py-2 px-4">
|
<div className="flex items-start gap-2 cursor-pointer py-2 px-4">
|
||||||
<div className="flex gap-2 items-center mt-1.5">
|
<div className="flex gap-2 items-center mt-1.5">
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { BIG_RELAY_URLS, ExtendedKind } from '@/constants'
|
import { BIG_RELAY_URLS, ExtendedKind, NOTIFICATION_LIST_STYLE } from '@/constants'
|
||||||
import { compareEvents } from '@/lib/event'
|
import { compareEvents } from '@/lib/event'
|
||||||
import { usePrimaryPage } from '@/PageManager'
|
import { usePrimaryPage } from '@/PageManager'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { useNotification } from '@/providers/NotificationProvider'
|
import { useNotification } from '@/providers/NotificationProvider'
|
||||||
|
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
|
||||||
import { useUserTrust } from '@/providers/UserTrustProvider'
|
import { useUserTrust } from '@/providers/UserTrustProvider'
|
||||||
import client from '@/services/client.service'
|
import client from '@/services/client.service'
|
||||||
import noteStatsService from '@/services/note-stats.service'
|
import noteStatsService from '@/services/note-stats.service'
|
||||||
@@ -34,6 +35,7 @@ const NotificationList = forwardRef((_, ref) => {
|
|||||||
const { pubkey } = useNostr()
|
const { pubkey } = useNostr()
|
||||||
const { hideUntrustedNotifications, isUserTrusted } = useUserTrust()
|
const { hideUntrustedNotifications, isUserTrusted } = useUserTrust()
|
||||||
const { getNotificationsSeenAt } = useNotification()
|
const { getNotificationsSeenAt } = useNotification()
|
||||||
|
const { notificationListStyle } = useUserPreferences()
|
||||||
const [notificationType, setNotificationType] = useState<TNotificationType>('all')
|
const [notificationType, setNotificationType] = useState<TNotificationType>('all')
|
||||||
const [lastReadTime, setLastReadTime] = useState(0)
|
const [lastReadTime, setLastReadTime] = useState(0)
|
||||||
const [refreshCount, setRefreshCount] = useState(0)
|
const [refreshCount, setRefreshCount] = useState(0)
|
||||||
@@ -262,7 +264,7 @@ const NotificationList = forwardRef((_, ref) => {
|
|||||||
}}
|
}}
|
||||||
pullingContent=""
|
pullingContent=""
|
||||||
>
|
>
|
||||||
<div>
|
<div className={notificationListStyle === NOTIFICATION_LIST_STYLE.COMPACT ? 'pt-2' : ''}>
|
||||||
{visibleNotifications.map((notification) => (
|
{visibleNotifications.map((notification) => (
|
||||||
<NotificationItem
|
<NotificationItem
|
||||||
key={notification.id}
|
key={notification.id}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export const StorageKey = {
|
|||||||
SHOW_KINDS: 'showKinds',
|
SHOW_KINDS: 'showKinds',
|
||||||
SHOW_KINDS_VERSION: 'showKindsVersion',
|
SHOW_KINDS_VERSION: 'showKindsVersion',
|
||||||
HIDE_CONTENT_MENTIONING_MUTED_USERS: 'hideContentMentioningMutedUsers',
|
HIDE_CONTENT_MENTIONING_MUTED_USERS: 'hideContentMentioningMutedUsers',
|
||||||
|
NOTIFICATION_LIST_STYLE: 'notificationListStyle',
|
||||||
MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated
|
MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated
|
||||||
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated
|
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated
|
||||||
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated
|
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated
|
||||||
@@ -136,3 +137,8 @@ export const POLL_TYPE = {
|
|||||||
MULTIPLE_CHOICE: 'multiplechoice',
|
MULTIPLE_CHOICE: 'multiplechoice',
|
||||||
SINGLE_CHOICE: 'singlechoice'
|
SINGLE_CHOICE: 'singlechoice'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
export const NOTIFICATION_LIST_STYLE = {
|
||||||
|
COMPACT: 'compact',
|
||||||
|
DETAILED: 'detailed'
|
||||||
|
} as const
|
||||||
|
|||||||
@@ -392,6 +392,11 @@ export default {
|
|||||||
profanity: 'ألفاظ نابية',
|
profanity: 'ألفاظ نابية',
|
||||||
illegal: 'محتوى غير قانوني',
|
illegal: 'محتوى غير قانوني',
|
||||||
spam: 'رسائل مزعجة',
|
spam: 'رسائل مزعجة',
|
||||||
other: 'أخرى'
|
other: 'أخرى',
|
||||||
|
'Notification list style': 'نمط قائمة الإشعارات',
|
||||||
|
'See extra info for each notification': 'عرض معلومات إضافية لكل إشعار',
|
||||||
|
'See more notifications at a glance': 'رؤية المزيد من الإشعارات بنظرة سريعة',
|
||||||
|
Detailed: 'تفصيلي',
|
||||||
|
Compact: 'مضغوط'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -401,6 +401,12 @@ export default {
|
|||||||
profanity: 'Obszönität',
|
profanity: 'Obszönität',
|
||||||
illegal: 'Illegaler Inhalt',
|
illegal: 'Illegaler Inhalt',
|
||||||
spam: 'Spam',
|
spam: 'Spam',
|
||||||
other: 'Sonstiges'
|
other: 'Sonstiges',
|
||||||
|
'Notification list style': 'Benachrichtigungslistenstil',
|
||||||
|
'See extra info for each notification':
|
||||||
|
'Zusätzliche Informationen für jede Benachrichtigung anzeigen',
|
||||||
|
'See more notifications at a glance': 'Mehr Benachrichtigungen auf einen Blick sehen',
|
||||||
|
Detailed: 'Detailliert',
|
||||||
|
Compact: 'Kompakt'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -391,6 +391,11 @@ export default {
|
|||||||
profanity: 'Profanity',
|
profanity: 'Profanity',
|
||||||
illegal: 'Illegal content',
|
illegal: 'Illegal content',
|
||||||
spam: 'Spam',
|
spam: 'Spam',
|
||||||
other: 'Other'
|
other: 'Other',
|
||||||
|
'Notification list style': 'Notification list style',
|
||||||
|
'See extra info for each notification': 'See extra info for each notification',
|
||||||
|
'See more notifications at a glance': 'See more notifications at a glance',
|
||||||
|
Detailed: 'Detailed',
|
||||||
|
Compact: 'Compact'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -397,6 +397,11 @@ export default {
|
|||||||
profanity: 'Blasfemia',
|
profanity: 'Blasfemia',
|
||||||
illegal: 'Contenido ilegal',
|
illegal: 'Contenido ilegal',
|
||||||
spam: 'Spam',
|
spam: 'Spam',
|
||||||
other: 'Otro'
|
other: 'Otro',
|
||||||
|
'Notification list style': 'Estilo de lista de notificaciones',
|
||||||
|
'See extra info for each notification': 'Ver información adicional para cada notificación',
|
||||||
|
'See more notifications at a glance': 'Ver más notificaciones de un vistazo',
|
||||||
|
Detailed: 'Detallado',
|
||||||
|
Compact: 'Compacto'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -393,6 +393,11 @@ export default {
|
|||||||
profanity: 'فحاشی',
|
profanity: 'فحاشی',
|
||||||
illegal: 'محتوای غیرقانونی',
|
illegal: 'محتوای غیرقانونی',
|
||||||
spam: 'اسپم',
|
spam: 'اسپم',
|
||||||
other: 'سایر'
|
other: 'سایر',
|
||||||
|
'Notification list style': 'سبک فهرست اعلانها',
|
||||||
|
'See extra info for each notification': 'مشاهده اطلاعات اضافی برای هر اعلان',
|
||||||
|
'See more notifications at a glance': 'مشاهده اعلانهای بیشتر در یک نگاه',
|
||||||
|
Detailed: 'تفصیلی',
|
||||||
|
Compact: 'فشرده'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -401,6 +401,12 @@ export default {
|
|||||||
profanity: 'Blasphème',
|
profanity: 'Blasphème',
|
||||||
illegal: 'Contenu illégal',
|
illegal: 'Contenu illégal',
|
||||||
spam: 'Spam',
|
spam: 'Spam',
|
||||||
other: 'Autre'
|
other: 'Autre',
|
||||||
|
'Notification list style': 'Style de liste de notifications',
|
||||||
|
'See extra info for each notification':
|
||||||
|
'Voir des infos supplémentaires pour chaque notification',
|
||||||
|
'See more notifications at a glance': "Voir plus de notifications en un coup d'œil",
|
||||||
|
Detailed: 'Détaillé',
|
||||||
|
Compact: 'Compact'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -397,6 +397,11 @@ export default {
|
|||||||
profanity: 'Blasfemia',
|
profanity: 'Blasfemia',
|
||||||
illegal: 'Contenuto illegale',
|
illegal: 'Contenuto illegale',
|
||||||
spam: 'Spam',
|
spam: 'Spam',
|
||||||
other: 'Altro'
|
other: 'Altro',
|
||||||
|
'Notification list style': 'Stile elenco notifiche',
|
||||||
|
'See extra info for each notification': 'Visualizza informazioni extra per ogni notifica',
|
||||||
|
'See more notifications at a glance': "Visualizza più notifiche a colpo d'occhio",
|
||||||
|
Detailed: 'Dettagliato',
|
||||||
|
Compact: 'Compatto'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,6 +394,11 @@ export default {
|
|||||||
profanity: '冒涜的な内容',
|
profanity: '冒涜的な内容',
|
||||||
illegal: '違法コンテンツ',
|
illegal: '違法コンテンツ',
|
||||||
spam: 'スパム',
|
spam: 'スパム',
|
||||||
other: 'その他'
|
other: 'その他',
|
||||||
|
'Notification list style': '通知リストスタイル',
|
||||||
|
'See extra info for each notification': '各通知の詳細情報を表示',
|
||||||
|
'See more notifications at a glance': '一目でより多くの通知を確認',
|
||||||
|
Detailed: '詳細',
|
||||||
|
Compact: 'コンパクト'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,6 +394,11 @@ export default {
|
|||||||
profanity: '욕설',
|
profanity: '욕설',
|
||||||
illegal: '불법 콘텐츠',
|
illegal: '불법 콘텐츠',
|
||||||
spam: '스팸',
|
spam: '스팸',
|
||||||
other: '기타'
|
other: '기타',
|
||||||
|
'Notification list style': '알림 목록 스타일',
|
||||||
|
'See extra info for each notification': '각 알림의 추가 정보 보기',
|
||||||
|
'See more notifications at a glance': '한눈에 더 많은 알림 보기',
|
||||||
|
Detailed: '상세',
|
||||||
|
Compact: '간단'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -398,6 +398,11 @@ export default {
|
|||||||
profanity: 'Wulgaryzmy',
|
profanity: 'Wulgaryzmy',
|
||||||
illegal: 'Nielegalna treść',
|
illegal: 'Nielegalna treść',
|
||||||
spam: 'Spam',
|
spam: 'Spam',
|
||||||
other: 'Inne'
|
other: 'Inne',
|
||||||
|
'Notification list style': 'Styl listy powiadomień',
|
||||||
|
'See extra info for each notification': 'Zobacz dodatkowe informacje dla każdego powiadomienia',
|
||||||
|
'See more notifications at a glance': 'Zobacz więcej powiadomień na pierwszy rzut oka',
|
||||||
|
Detailed: 'Szczegółowy',
|
||||||
|
Compact: 'Zwięzły'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,6 +394,11 @@ export default {
|
|||||||
profanity: 'Blasfêmia',
|
profanity: 'Blasfêmia',
|
||||||
illegal: 'Conteúdo ilegal',
|
illegal: 'Conteúdo ilegal',
|
||||||
spam: 'Spam',
|
spam: 'Spam',
|
||||||
other: 'Outro'
|
other: 'Outro',
|
||||||
|
'Notification list style': 'Estilo da lista de notificações',
|
||||||
|
'See extra info for each notification': 'Ver informações extras para cada notificação',
|
||||||
|
'See more notifications at a glance': 'Ver mais notificações rapidamente',
|
||||||
|
Detailed: 'Detalhado',
|
||||||
|
Compact: 'Compacto'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -397,6 +397,11 @@ export default {
|
|||||||
profanity: 'Blasfémia',
|
profanity: 'Blasfémia',
|
||||||
illegal: 'Conteúdo ilegal',
|
illegal: 'Conteúdo ilegal',
|
||||||
spam: 'Spam',
|
spam: 'Spam',
|
||||||
other: 'Outro'
|
other: 'Outro',
|
||||||
|
'Notification list style': 'Estilo da lista de notificações',
|
||||||
|
'See extra info for each notification': 'Ver informações extra para cada notificação',
|
||||||
|
'See more notifications at a glance': 'Ver mais notificações rapidamente',
|
||||||
|
Detailed: 'Detalhado',
|
||||||
|
Compact: 'Compacto'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -398,6 +398,12 @@ export default {
|
|||||||
profanity: 'Ненормативная лексика',
|
profanity: 'Ненормативная лексика',
|
||||||
illegal: 'Незаконный контент',
|
illegal: 'Незаконный контент',
|
||||||
spam: 'Спам',
|
spam: 'Спам',
|
||||||
other: 'Другое'
|
other: 'Другое',
|
||||||
|
'Notification list style': 'Стиль списка уведомлений',
|
||||||
|
'See extra info for each notification':
|
||||||
|
'Просмотреть дополнительную информацию для каждого уведомления',
|
||||||
|
'See more notifications at a glance': 'Увидеть больше уведомлений с первого взгляда',
|
||||||
|
Detailed: 'Подробный',
|
||||||
|
Compact: 'Компактный'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -389,6 +389,11 @@ export default {
|
|||||||
profanity: 'คำหยาบคาย',
|
profanity: 'คำหยาบคาย',
|
||||||
illegal: 'เนื้อหาผิดกฎหมาย',
|
illegal: 'เนื้อหาผิดกฎหมาย',
|
||||||
spam: 'สแปม',
|
spam: 'สแปม',
|
||||||
other: 'อื่นๆ'
|
other: 'อื่นๆ',
|
||||||
|
'Notification list style': 'รูปแบบรายการการแจ้งเตือน',
|
||||||
|
'See extra info for each notification': 'ดูข้อมูลเพิ่มเติมสำหรับการแจ้งเตือนแต่ละรายการ',
|
||||||
|
'See more notifications at a glance': 'ดูการแจ้งเตือนเพิ่มเติมในแวบเดียว',
|
||||||
|
Detailed: 'รายละเอียด',
|
||||||
|
Compact: 'กะทัดรัด'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -387,6 +387,11 @@ export default {
|
|||||||
profanity: '亵渎言论',
|
profanity: '亵渎言论',
|
||||||
illegal: '违法内容',
|
illegal: '违法内容',
|
||||||
spam: '垃圾信息',
|
spam: '垃圾信息',
|
||||||
other: '其他'
|
other: '其他',
|
||||||
|
'Notification list style': '通知列表样式',
|
||||||
|
'See extra info for each notification': '查看每条通知的详细信息',
|
||||||
|
'See more notifications at a glance': '一目了然地查看更多通知',
|
||||||
|
Detailed: '详细',
|
||||||
|
Compact: '紧凑'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select'
|
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select'
|
||||||
import { Switch } from '@/components/ui/switch'
|
import { Switch } from '@/components/ui/switch'
|
||||||
|
import { NOTIFICATION_LIST_STYLE } from '@/constants'
|
||||||
import { LocalizedLanguageNames, TLanguage } from '@/i18n'
|
import { LocalizedLanguageNames, TLanguage } from '@/i18n'
|
||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
||||||
import { useTheme } from '@/providers/ThemeProvider'
|
import { useTheme } from '@/providers/ThemeProvider'
|
||||||
|
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
|
||||||
import { useUserTrust } from '@/providers/UserTrustProvider'
|
import { useUserTrust } from '@/providers/UserTrustProvider'
|
||||||
import { SelectValue } from '@radix-ui/react-select'
|
import { SelectValue } from '@radix-ui/react-select'
|
||||||
import { ExternalLink } from 'lucide-react'
|
import { ExternalLink } from 'lucide-react'
|
||||||
@@ -25,6 +27,7 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
|||||||
setHideContentMentioningMutedUsers
|
setHideContentMentioningMutedUsers
|
||||||
} = useContentPolicy()
|
} = useContentPolicy()
|
||||||
const { hideUntrustedNotes, updateHideUntrustedNotes } = useUserTrust()
|
const { hideUntrustedNotes, updateHideUntrustedNotes } = useUserTrust()
|
||||||
|
const { notificationListStyle, updateNotificationListStyle } = useUserPreferences()
|
||||||
|
|
||||||
const handleLanguageChange = (value: TLanguage) => {
|
const handleLanguageChange = (value: TLanguage) => {
|
||||||
i18n.changeLanguage(value)
|
i18n.changeLanguage(value)
|
||||||
@@ -66,6 +69,29 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
<SettingItem>
|
||||||
|
<Label htmlFor="notification-list-style" className="text-base font-normal">
|
||||||
|
<div>{t('Notification list style')}</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
{notificationListStyle === NOTIFICATION_LIST_STYLE.DETAILED
|
||||||
|
? t('See extra info for each notification')
|
||||||
|
: t('See more notifications at a glance')}
|
||||||
|
</div>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
defaultValue={NOTIFICATION_LIST_STYLE.DETAILED}
|
||||||
|
value={notificationListStyle}
|
||||||
|
onValueChange={updateNotificationListStyle}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="notification-list-style" className="w-48">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value={NOTIFICATION_LIST_STYLE.DETAILED}>{t('Detailed')}</SelectItem>
|
||||||
|
<SelectItem value={NOTIFICATION_LIST_STYLE.COMPACT}>{t('Compact')}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</SettingItem>
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<Label htmlFor="autoplay" className="text-base font-normal">
|
<Label htmlFor="autoplay" className="text-base font-normal">
|
||||||
<div>{t('Autoplay')}</div>
|
<div>{t('Autoplay')}</div>
|
||||||
|
|||||||
40
src/providers/UserPreferencesProvider.tsx
Normal file
40
src/providers/UserPreferencesProvider.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import storage from '@/services/local-storage.service'
|
||||||
|
import { TNotificationStyle } from '@/types'
|
||||||
|
import { createContext, useContext, useState } from 'react'
|
||||||
|
|
||||||
|
type TUserPreferencesContext = {
|
||||||
|
notificationListStyle: TNotificationStyle
|
||||||
|
updateNotificationListStyle: (style: TNotificationStyle) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserPreferencesContext = createContext<TUserPreferencesContext | undefined>(undefined)
|
||||||
|
|
||||||
|
export const useUserPreferences = () => {
|
||||||
|
const context = useContext(UserPreferencesContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useUserPreferences must be used within a UserPreferencesProvider')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UserPreferencesProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [notificationListStyle, setNotificationListStyle] = useState(
|
||||||
|
storage.getNotificationListStyle()
|
||||||
|
)
|
||||||
|
|
||||||
|
const updateNotificationListStyle = (style: TNotificationStyle) => {
|
||||||
|
setNotificationListStyle(style)
|
||||||
|
storage.setNotificationListStyle(style)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserPreferencesContext.Provider
|
||||||
|
value={{
|
||||||
|
notificationListStyle,
|
||||||
|
updateNotificationListStyle
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</UserPreferencesContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
import { DEFAULT_NIP_96_SERVICE, ExtendedKind, SUPPORTED_KINDS, StorageKey } from '@/constants'
|
import {
|
||||||
|
DEFAULT_NIP_96_SERVICE,
|
||||||
|
ExtendedKind,
|
||||||
|
NOTIFICATION_LIST_STYLE,
|
||||||
|
SUPPORTED_KINDS,
|
||||||
|
StorageKey
|
||||||
|
} from '@/constants'
|
||||||
import { isSameAccount } from '@/lib/account'
|
import { isSameAccount } from '@/lib/account'
|
||||||
import { randomString } from '@/lib/random'
|
import { randomString } from '@/lib/random'
|
||||||
import {
|
import {
|
||||||
@@ -7,6 +13,7 @@ import {
|
|||||||
TFeedInfo,
|
TFeedInfo,
|
||||||
TMediaUploadServiceConfig,
|
TMediaUploadServiceConfig,
|
||||||
TNoteListMode,
|
TNoteListMode,
|
||||||
|
TNotificationStyle,
|
||||||
TRelaySet,
|
TRelaySet,
|
||||||
TThemeSetting,
|
TThemeSetting,
|
||||||
TTranslationServiceConfig
|
TTranslationServiceConfig
|
||||||
@@ -36,6 +43,7 @@ class LocalStorageService {
|
|||||||
private dismissedTooManyRelaysAlert: boolean = false
|
private dismissedTooManyRelaysAlert: boolean = false
|
||||||
private showKinds: number[] = []
|
private showKinds: number[] = []
|
||||||
private hideContentMentioningMutedUsers: boolean = false
|
private hideContentMentioningMutedUsers: boolean = false
|
||||||
|
private notificationListStyle: TNotificationStyle = NOTIFICATION_LIST_STYLE.DETAILED
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (!LocalStorageService.instance) {
|
if (!LocalStorageService.instance) {
|
||||||
@@ -160,6 +168,12 @@ class LocalStorageService {
|
|||||||
this.hideContentMentioningMutedUsers =
|
this.hideContentMentioningMutedUsers =
|
||||||
window.localStorage.getItem(StorageKey.HIDE_CONTENT_MENTIONING_MUTED_USERS) === 'true'
|
window.localStorage.getItem(StorageKey.HIDE_CONTENT_MENTIONING_MUTED_USERS) === 'true'
|
||||||
|
|
||||||
|
this.notificationListStyle =
|
||||||
|
window.localStorage.getItem(StorageKey.NOTIFICATION_LIST_STYLE) ===
|
||||||
|
NOTIFICATION_LIST_STYLE.COMPACT
|
||||||
|
? NOTIFICATION_LIST_STYLE.COMPACT
|
||||||
|
: NOTIFICATION_LIST_STYLE.DETAILED
|
||||||
|
|
||||||
// Clean up deprecated data
|
// Clean up deprecated data
|
||||||
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
|
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
|
||||||
window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP)
|
window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP)
|
||||||
@@ -410,6 +424,15 @@ class LocalStorageService {
|
|||||||
this.hideContentMentioningMutedUsers = hide
|
this.hideContentMentioningMutedUsers = hide
|
||||||
window.localStorage.setItem(StorageKey.HIDE_CONTENT_MENTIONING_MUTED_USERS, hide.toString())
|
window.localStorage.setItem(StorageKey.HIDE_CONTENT_MENTIONING_MUTED_USERS, hide.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNotificationListStyle() {
|
||||||
|
return this.notificationListStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
setNotificationListStyle(style: TNotificationStyle) {
|
||||||
|
this.notificationListStyle = style
|
||||||
|
window.localStorage.setItem(StorageKey.NOTIFICATION_LIST_STYLE, style)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = new LocalStorageService()
|
const instance = new LocalStorageService()
|
||||||
|
|||||||
5
src/types/index.d.ts
vendored
5
src/types/index.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
import { Event, VerifiedEvent, Filter } from 'nostr-tools'
|
import { Event, VerifiedEvent, Filter } from 'nostr-tools'
|
||||||
import { POLL_TYPE } from './constants'
|
import { NOTIFICATION_LIST_STYLE, POLL_TYPE } from '../constants'
|
||||||
|
|
||||||
export type TSubRequestFilter = Omit<Filter, 'since' | 'until'> & { limit: number }
|
export type TSubRequestFilter = Omit<Filter, 'since' | 'until'> & { limit: number }
|
||||||
|
|
||||||
@@ -182,3 +182,6 @@ export type TSearchParams = {
|
|||||||
search: string
|
search: string
|
||||||
input?: string
|
input?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TNotificationStyle =
|
||||||
|
(typeof NOTIFICATION_LIST_STYLE)[keyof typeof NOTIFICATION_LIST_STYLE]
|
||||||
|
|||||||
Reference in New Issue
Block a user