From 917fcd9839dae32a644235eb8522bce21326d608 Mon Sep 17 00:00:00 2001 From: codytseng Date: Sun, 4 Jan 2026 20:42:20 +0800 Subject: [PATCH] feat: live feed toggle --- src/components/LiveFeedToggle/index.tsx | 28 ++++++++++++++++++++ src/components/NormalFeed/index.tsx | 8 +++++- src/components/Profile/ProfileFeed.tsx | 6 ++++- src/components/TrustScoreFilter/index.tsx | 2 +- src/components/UserAggregationList/index.tsx | 14 +++++++--- src/constants.ts | 1 + src/i18n/locales/ar.ts | 4 ++- src/i18n/locales/de.ts | 4 ++- src/i18n/locales/en.ts | 4 ++- src/i18n/locales/es.ts | 4 ++- src/i18n/locales/fa.ts | 4 ++- src/i18n/locales/fr.ts | 4 ++- src/i18n/locales/hi.ts | 4 ++- src/i18n/locales/hu.ts | 4 ++- src/i18n/locales/it.ts | 4 ++- src/i18n/locales/ja.ts | 4 ++- src/i18n/locales/ko.ts | 4 ++- src/i18n/locales/pl.ts | 4 ++- src/i18n/locales/pt-BR.ts | 4 ++- src/i18n/locales/pt-PT.ts | 4 ++- src/i18n/locales/ru.ts | 4 ++- src/i18n/locales/th.ts | 4 ++- src/i18n/locales/zh-TW.ts | 4 ++- src/i18n/locales/zh.ts | 4 ++- src/providers/UserPreferencesProvider.tsx | 13 ++++++++- src/services/local-storage.service.ts | 12 +++++++++ 26 files changed, 130 insertions(+), 26 deletions(-) create mode 100644 src/components/LiveFeedToggle/index.tsx diff --git a/src/components/LiveFeedToggle/index.tsx b/src/components/LiveFeedToggle/index.tsx new file mode 100644 index 0000000..a93d317 --- /dev/null +++ b/src/components/LiveFeedToggle/index.tsx @@ -0,0 +1,28 @@ +import { Button } from '@/components/ui/button' +import { cn } from '@/lib/utils' +import { useUserPreferences } from '@/providers/UserPreferencesProvider' +import { Radio } from 'lucide-react' +import { useTranslation } from 'react-i18next' + +export function LiveFeedToggle() { + const { t } = useTranslation() + const { enableLiveFeed, updateEnableLiveFeed } = useUserPreferences() + + return ( + + ) +} diff --git a/src/components/NormalFeed/index.tsx b/src/components/NormalFeed/index.tsx index f6b4900..5ca5dd1 100644 --- a/src/components/NormalFeed/index.tsx +++ b/src/components/NormalFeed/index.tsx @@ -9,6 +9,8 @@ import { TFeedSubRequest, TNoteListMode } from '@/types' import { useMemo, useRef, useState } from 'react' import KindFilter from '../KindFilter' import { RefreshButton } from '../RefreshButton' +import { LiveFeedToggle } from '../LiveFeedToggle' +import { useUserPreferences } from '@/providers/UserPreferencesProvider' export default function NormalFeed({ subRequests, @@ -28,6 +30,7 @@ export default function NormalFeed({ isPubkeyFeed?: boolean }) { const { showKinds } = useKindFilter() + const { enableLiveFeed } = useUserPreferences() const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds) const [listMode, setListMode] = useState(() => storage.getNoteListMode()) const supportTouch = useMemo(() => isTouchDevice(), []) @@ -85,7 +88,8 @@ export default function NormalFeed({ }} /> )} - + + {!isPubkeyFeed && } {showKindsFilter && ( ) : ( )} diff --git a/src/components/Profile/ProfileFeed.tsx b/src/components/Profile/ProfileFeed.tsx index 363f7d7..25e94da 100644 --- a/src/components/Profile/ProfileFeed.tsx +++ b/src/components/Profile/ProfileFeed.tsx @@ -6,12 +6,14 @@ import { generateBech32IdFromETag } from '@/lib/tag' import { isTouchDevice } from '@/lib/utils' import { useKindFilter } from '@/providers/KindFilterProvider' import { useNostr } from '@/providers/NostrProvider' +import { useUserPreferences } from '@/providers/UserPreferencesProvider' import client from '@/services/client.service' import storage from '@/services/local-storage.service' import relayInfoService from '@/services/relay-info.service' import { TFeedSubRequest, TNoteListMode } from '@/types' import { NostrEvent } from 'nostr-tools' import { useEffect, useMemo, useRef, useState } from 'react' +import { LiveFeedToggle } from '../LiveFeedToggle' import { RefreshButton } from '../RefreshButton' export default function ProfileFeed({ @@ -24,6 +26,7 @@ export default function ProfileFeed({ search?: string }) { const { pubkey: myPubkey, pinListEvent: myPinListEvent } = useNostr() + const { enableLiveFeed } = useUserPreferences() const { showKinds } = useKindFilter() const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds) const [listMode, setListMode] = useState(() => { @@ -164,6 +167,7 @@ export default function ProfileFeed({ options={ <> {!supportTouch && noteListRef.current?.refresh()} />} + } @@ -175,7 +179,7 @@ export default function ProfileFeed({ hideReplies={listMode === 'posts'} filterMutedNotes={false} pinnedEventIds={listMode === 'you' || !!search ? [] : pinnedEventIds} - showNewNotesDirectly={myPubkey === pubkey} + showNewNotesDirectly={myPubkey === pubkey || enableLiveFeed} /> ) diff --git a/src/components/TrustScoreFilter/index.tsx b/src/components/TrustScoreFilter/index.tsx index 5b88f94..22fd4e2 100644 --- a/src/components/TrustScoreFilter/index.tsx +++ b/src/components/TrustScoreFilter/index.tsx @@ -91,7 +91,7 @@ export default function TrustScoreFilter({ > {minTrustScore < 100 ? : } {minTrustScore > 0 && minTrustScore < 100 && ( -
+
{minTrustScore}
)} diff --git a/src/components/UserAggregationList/index.tsx b/src/components/UserAggregationList/index.tsx index bf8dfe1..5f2d839 100644 --- a/src/components/UserAggregationList/index.tsx +++ b/src/components/UserAggregationList/index.tsx @@ -54,6 +54,7 @@ const UserAggregationList = forwardRef< areAlgoRelays?: boolean showRelayCloseReason?: boolean isPubkeyFeed?: boolean + showNewNotesDirectly?: boolean } >( ( @@ -63,7 +64,8 @@ const UserAggregationList = forwardRef< filterMutedNotes = true, areAlgoRelays = false, showRelayCloseReason = false, - isPubkeyFeed = false + isPubkeyFeed = false, + showNewNotesDirectly = false }, ref ) => { @@ -176,9 +178,13 @@ const UserAggregationList = forwardRef< } }, onNew: (event) => { - setNewEvents((oldEvents) => - [event, ...oldEvents].sort((a, b) => b.created_at - a.created_at) - ) + if (showNewNotesDirectly) { + setEvents((oldEvents) => [event, ...oldEvents]) + } else { + setNewEvents((oldEvents) => + [event, ...oldEvents].sort((a, b) => b.created_at - a.created_at) + ) + } threadService.addRepliesToThread([event]) }, onClose: (url, reason) => { diff --git a/src/constants.ts b/src/constants.ts index 8f4b9d9..bc8ff6e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -42,6 +42,7 @@ export const StorageKey = { QUICK_REACTION_EMOJI: 'quickReactionEmoji', NSFW_DISPLAY_POLICY: 'nsfwDisplayPolicy', MIN_TRUST_SCORE: 'minTrustScore', + ENABLE_LIVE_FEED: 'enableLiveFeed', HIDE_UNTRUSTED_NOTES: 'hideUntrustedNotes', // deprecated HIDE_UNTRUSTED_INTERACTIONS: 'hideUntrustedInteractions', // deprecated HIDE_UNTRUSTED_NOTIFICATIONS: 'hideUntrustedNotifications', // deprecated diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index 65c6ba5..df5308c 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -651,6 +651,8 @@ export default { 'trust-filter.hide-bottom-percent': 'تصفية أدنى {{score}}٪ من المستخدمين حسب تصنيف الثقة', 'trust-filter.trust-score-description': 'محسوبة بناءً على سمعة المستخدم والنسبة المئوية للشبكة الاجتماعية', - 'Auto-load profile pictures': 'تحميل صور الملف الشخصي تلقائيًا' + 'Auto-load profile pictures': 'تحميل صور الملف الشخصي تلقائيًا', + 'Disable live feed': 'تعطيل التغذية المباشرة', + 'Enable live feed': 'تفعيل التغذية المباشرة' } } diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index dc39e26..aa690c9 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -673,6 +673,8 @@ export default { 'Untere {{score}}% der Benutzer nach Vertrauensrang filtern', 'trust-filter.trust-score-description': 'Berechnet basierend auf Benutzerreputation und sozialem Netzwerk-Perzentil', - 'Auto-load profile pictures': 'Profilbilder automatisch laden' + 'Auto-load profile pictures': 'Profilbilder automatisch laden', + 'Disable live feed': 'Live-Feed deaktivieren', + 'Enable live feed': 'Live-Feed aktivieren' } } diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 6d49db2..c791db1 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -656,6 +656,8 @@ export default { 'trust-filter.hide-bottom-percent': 'Filter out bottom {{score}}% of users by trust rank', 'trust-filter.trust-score-description': 'Calculated based on user reputation and social network percentile', - 'Auto-load profile pictures': 'Auto-load profile pictures' + 'Auto-load profile pictures': 'Auto-load profile pictures', + 'Disable live feed': 'Disable live feed', + 'Enable live feed': 'Enable live feed' } } diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index 1ae434b..3629b8e 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -667,6 +667,8 @@ export default { 'Filtrar el {{score}}% inferior de usuarios por clasificación de confianza', 'trust-filter.trust-score-description': 'Calculado según la reputación del usuario y el percentil de la red social', - 'Auto-load profile pictures': 'Cargar imágenes de perfil automáticamente' + 'Auto-load profile pictures': 'Cargar imágenes de perfil automáticamente', + 'Disable live feed': 'Desactivar feed en vivo', + 'Enable live feed': 'Activar feed en vivo' } } diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts index bb168dc..09199de 100644 --- a/src/i18n/locales/fa.ts +++ b/src/i18n/locales/fa.ts @@ -663,6 +663,8 @@ export default { 'فیلتر کردن {{score}}٪ پایین‌ترین کاربران بر اساس رتبه اعتماد', 'trust-filter.trust-score-description': 'بر اساس شهرت کاربر و صدک شبکه اجتماعی محاسبه می‌شود', - 'Auto-load profile pictures': 'بارگذاری خودکار تصاویر پروفایل' + 'Auto-load profile pictures': 'بارگذاری خودکار تصاویر پروفایل', + 'Disable live feed': 'غیرفعال کردن فید زنده', + 'Enable live feed': 'فعال کردن فید زنده' } } diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index b302af4..4f2c179 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -671,6 +671,8 @@ export default { 'Filtrer les {{score}}% inférieurs des utilisateurs par classement de confiance', 'trust-filter.trust-score-description': "Calculé en fonction de la réputation et du réseau social de l'utilisateur", - 'Auto-load profile pictures': 'Charger les images de profil automatiquement' + 'Auto-load profile pictures': 'Charger les images de profil automatiquement', + 'Disable live feed': 'Désactiver le flux en direct', + 'Enable live feed': 'Activer le flux en direct' } } diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index c19b46d..2b29179 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -663,6 +663,8 @@ export default { 'विश्वास रैंक द्वारा निचले {{score}}% उपयोगकर्ताओं को फ़िल्टर करें', 'trust-filter.trust-score-description': 'उपयोगकर्ता की प्रतिष्ठा और सामाजिक नेटवर्क प्रतिशतक के आधार पर गणना की गई', - 'Auto-load profile pictures': 'प्रोफ़ाइल चित्र स्वतः लोड करें' + 'Auto-load profile pictures': 'प्रोफ़ाइल चित्र स्वतः लोड करें', + 'Disable live feed': 'लाइव फ़ीड अक्षम करें', + 'Enable live feed': 'लाइव फ़ीड सक्षम करें' } } diff --git a/src/i18n/locales/hu.ts b/src/i18n/locales/hu.ts index f577d60..f47fa89 100644 --- a/src/i18n/locales/hu.ts +++ b/src/i18n/locales/hu.ts @@ -656,6 +656,8 @@ export default { 'Alsó {{score}}% felhasználók szűrése bizalmi rangsor szerint', 'trust-filter.trust-score-description': 'A felhasználó hírneve és a közösségi hálózat percentilise alapján számítva', - 'Auto-load profile pictures': 'Profilképek automatikus betöltése' + 'Auto-load profile pictures': 'Profilképek automatikus betöltése', + 'Disable live feed': 'Élő hírfolyam letiltása', + 'Enable live feed': 'Élő hírfolyam engedélyezése' } } diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts index 1dde470..6097c8f 100644 --- a/src/i18n/locales/it.ts +++ b/src/i18n/locales/it.ts @@ -667,6 +667,8 @@ export default { 'Filtra il {{score}}% inferiore degli utenti per classifica di fiducia', 'trust-filter.trust-score-description': "Calcolato in base alla reputazione dell'utente e al percentile del social network", - 'Auto-load profile pictures': 'Caricamento automatico immagini di profilo' + 'Auto-load profile pictures': 'Caricamento automatico immagini di profilo', + 'Disable live feed': 'Disattiva feed live', + 'Enable live feed': 'Attiva feed live' } } diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index 76cae98..dc9610e 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -662,6 +662,8 @@ export default { '信頼ランク下位 {{score}}% のユーザーをフィルタリング', 'trust-filter.trust-score-description': 'ユーザーの評判とソーシャルネットワークに基づいて信頼度パーセンタイルを計算', - 'Auto-load profile pictures': 'プロフィール画像を自動読み込み' + 'Auto-load profile pictures': 'プロフィール画像を自動読み込み', + 'Disable live feed': 'ライブフィードを無効にする', + 'Enable live feed': 'ライブフィードを有効にする' } } diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index 236003e..232cb9c 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -656,6 +656,8 @@ export default { 'trust-filter.only-show-wot': '신뢰 네트워크만 표시 (팔로우 + 팔로우의 팔로우)', 'trust-filter.hide-bottom-percent': '신뢰도 하위 {{score}}% 사용자 필터링', 'trust-filter.trust-score-description': '사용자의 평판과 소셜 네트워크를 기반으로 신뢰도 백분위수 계산', - 'Auto-load profile pictures': '프로필 사진 자동 로드' + 'Auto-load profile pictures': '프로필 사진 자동 로드', + 'Disable live feed': '라이브 피드 비활성화', + 'Enable live feed': '라이브 피드 활성화' } } diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index 29337b4..c18f87c 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -668,6 +668,8 @@ export default { 'Filtruj dolne {{score}}% użytkowników według rankingu zaufania', 'trust-filter.trust-score-description': 'Obliczany na podstawie reputacji użytkownika i percentyla sieci społecznościowej', - 'Auto-load profile pictures': 'Automatyczne ładowanie zdjęć profilowych' + 'Auto-load profile pictures': 'Automatyczne ładowanie zdjęć profilowych', + 'Disable live feed': 'Wyłącz kanał na żywo', + 'Enable live feed': 'Włącz kanał na żywo' } } diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts index e351aae..d69891c 100644 --- a/src/i18n/locales/pt-BR.ts +++ b/src/i18n/locales/pt-BR.ts @@ -664,6 +664,8 @@ export default { 'Filtrar os {{score}}% inferiores de usuários por classificação de confiança', 'trust-filter.trust-score-description': 'Calculado com base na reputação do usuário e no percentil da rede social', - 'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente' + 'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente', + 'Disable live feed': 'Desativar feed ao vivo', + 'Enable live feed': 'Ativar feed ao vivo' } } diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts index 7065b28..d08411f 100644 --- a/src/i18n/locales/pt-PT.ts +++ b/src/i18n/locales/pt-PT.ts @@ -667,6 +667,8 @@ export default { 'Filtrar os {{score}}% inferiores de utilizadores por classificação de confiança', 'trust-filter.trust-score-description': 'Calculado com base na reputação do utilizador e no percentil da rede social', - 'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente' + 'Auto-load profile pictures': 'Carregar fotos de perfil automaticamente', + 'Disable live feed': 'Desativar feed ao vivo', + 'Enable live feed': 'Ativar feed ao vivo' } } diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index 28d5fe1..3e303a7 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -667,6 +667,8 @@ export default { 'Отфильтровать нижние {{score}}% пользователей по рейтингу доверия', 'trust-filter.trust-score-description': 'Рассчитывается на основе репутации пользователя и процентиля социальной сети', - 'Auto-load profile pictures': 'Автозагрузка аватаров' + 'Auto-load profile pictures': 'Автозагрузка аватаров', + 'Disable live feed': 'Отключить прямую трансляцию', + 'Enable live feed': 'Включить прямую трансляцию' } } diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts index 1f132b5..75dcf26 100644 --- a/src/i18n/locales/th.ts +++ b/src/i18n/locales/th.ts @@ -653,6 +653,8 @@ export default { 'กรอง {{score}}% ล่างสุดของผู้ใช้ตามอันดับความไว้วางใจ', 'trust-filter.trust-score-description': 'คำนวณจากชื่อเสียงของผู้ใช้และเปอร์เซ็นไทล์ของเครือข่ายสังคม', - 'Auto-load profile pictures': 'โหลดรูปโปรไฟล์อัตโนมัติ' + 'Auto-load profile pictures': 'โหลดรูปโปรไฟล์อัตโนมัติ', + 'Disable live feed': 'ปิดฟีดสด', + 'Enable live feed': 'เปิดฟีดสด' } } diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index 95f9de9..ec9dccf 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -636,6 +636,8 @@ export default { 'trust-filter.only-show-wot': '僅顯示你的信任網路(關注的人 + 他們關注的人)', 'trust-filter.hide-bottom-percent': '過濾掉信任度排名後 {{score}}% 的使用者', 'trust-filter.trust-score-description': '基於使用者的聲譽和社交網路計算信任度百分位', - 'Auto-load profile pictures': '自動載入大頭照' + 'Auto-load profile pictures': '自動載入大頭照', + 'Disable live feed': '停用即時推送', + 'Enable live feed': '啟用即時推送' } } diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts index 4c440f5..23d3b3c 100644 --- a/src/i18n/locales/zh.ts +++ b/src/i18n/locales/zh.ts @@ -641,6 +641,8 @@ export default { 'trust-filter.only-show-wot': '仅显示你的信任网络(关注的人 + 他们关注的人)', 'trust-filter.hide-bottom-percent': '过滤掉信任度排名后 {{score}}% 的用户', 'trust-filter.trust-score-description': '基于用户的声誉和社交网络计算信任度百分位', - 'Auto-load profile pictures': '自动加载头像' + 'Auto-load profile pictures': '自动加载头像', + 'Disable live feed': '禁用实时推送', + 'Enable live feed': '启用实时推送' } } diff --git a/src/providers/UserPreferencesProvider.tsx b/src/providers/UserPreferencesProvider.tsx index 9302aef..a48a419 100644 --- a/src/providers/UserPreferencesProvider.tsx +++ b/src/providers/UserPreferencesProvider.tsx @@ -21,6 +21,9 @@ type TUserPreferencesContext = { quickReactionEmoji: string | TEmoji updateQuickReactionEmoji: (emoji: string | TEmoji) => void + + enableLiveFeed: boolean + updateEnableLiveFeed: (enable: boolean) => void } const UserPreferencesContext = createContext(undefined) @@ -45,6 +48,7 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod ) const [quickReaction, setQuickReaction] = useState(storage.getQuickReaction()) const [quickReactionEmoji, setQuickReactionEmoji] = useState(storage.getQuickReactionEmoji()) + const [enableLiveFeed, setEnableLiveFeed] = useState(storage.getEnableLiveFeed()) useEffect(() => { if (!isSmallScreen && enableSingleColumnLayout) { @@ -79,6 +83,11 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod storage.setQuickReactionEmoji(emoji) } + const updateEnableLiveFeed = (enable: boolean) => { + setEnableLiveFeed(enable) + storage.setEnableLiveFeed(enable) + } + return ( {children} diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts index 7056e53..6cb87ab 100644 --- a/src/services/local-storage.service.ts +++ b/src/services/local-storage.service.ts @@ -64,6 +64,7 @@ class LocalStorageService { private quickReactionEmoji: string | TEmoji = '+' private nsfwDisplayPolicy: TNsfwDisplayPolicy = NSFW_DISPLAY_POLICY.HIDE_CONTENT private minTrustScore: number = 40 + private enableLiveFeed: boolean = false constructor() { if (!LocalStorageService.instance) { @@ -275,6 +276,8 @@ class LocalStorageService { } } + this.enableLiveFeed = window.localStorage.getItem(StorageKey.ENABLE_LIVE_FEED) === 'true' + // Clean up deprecated data window.localStorage.removeItem(StorageKey.PINNED_PUBKEYS) window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP) @@ -612,6 +615,15 @@ class LocalStorageService { window.localStorage.setItem(StorageKey.MIN_TRUST_SCORE, score.toString()) } } + + getEnableLiveFeed() { + return this.enableLiveFeed + } + + setEnableLiveFeed(enable: boolean) { + this.enableLiveFeed = enable + window.localStorage.setItem(StorageKey.ENABLE_LIVE_FEED, enable.toString()) + } } const instance = new LocalStorageService()