From 519d0ba110e48801388412c5ba10d30367e5b689 Mon Sep 17 00:00:00 2001 From: Innei Date: Wed, 26 Nov 2025 17:35:24 +0800 Subject: [PATCH] feat: enhance feed management with filtering options - Added a responsive select component to filter feeds by type (All, RSSHub). - Implemented logic to count and display RSSHub feeds. - Updated feed selection and sorting functionality to respect the selected filter. - Enhanced localization files to include new filter labels in English, Japanese, Simplified Chinese, and Traditional Chinese. Signed-off-by: Innei --- .../src/modules/settings/tabs/feeds.tsx | 72 +++++++++++++++++-- locales/settings/en.json | 2 + locales/settings/ja.json | 2 + locales/settings/zh-CN.json | 2 + locales/settings/zh-TW.json | 2 + 5 files changed, 73 insertions(+), 7 deletions(-) diff --git a/apps/desktop/layer/renderer/src/modules/settings/tabs/feeds.tsx b/apps/desktop/layer/renderer/src/modules/settings/tabs/feeds.tsx index d820224a4..7d745c87a 100644 --- a/apps/desktop/layer/renderer/src/modules/settings/tabs/feeds.tsx +++ b/apps/desktop/layer/renderer/src/modules/settings/tabs/feeds.tsx @@ -6,6 +6,7 @@ import { LoadingCircle } from "@follow/components/ui/loading/index.jsx" import { RSSHubLogo } from "@follow/components/ui/platform-icon/icons.js" import { useScrollViewElement } from "@follow/components/ui/scroll-area/hooks.js" import { ScrollArea } from "@follow/components/ui/scroll-area/index.js" +import { ResponsiveSelect } from "@follow/components/ui/select/responsive.js" import { Table, TableBody, @@ -33,7 +34,7 @@ import { clsx, formatNumber, sortByAlphabet } from "@follow/utils/utils" import { useVirtualizer } from "@tanstack/react-virtual" import { AnimatePresence, m } from "motion/react" import type { FC } from "react" -import { memo, useCallback, useMemo, useState } from "react" +import { memo, useCallback, useEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { useIsInMASReview } from "~/atoms/server-configs" @@ -56,6 +57,7 @@ import { Queries } from "~/queries" type SortField = "name" | "view" | "date" | "subscriptionCount" | "updatesPerWeek" type SortDirection = "asc" | "desc" +type FeedFilter = "all" | "rsshub" export const SettingFeeds = () => { const inMas = useIsInMASReview() @@ -75,6 +77,42 @@ const SubscriptionFeedsSection = () => { const [selectedFeeds, setSelectedFeeds] = useState>(() => new Set()) const [sortField, setSortField] = useState("name") const [sortDirection, setSortDirection] = useState("asc") + const [filter, setFilter] = useState("all") + + // Calculate RSSHub feeds count + const rsshubFeedsCount = useMemo(() => { + return allFeeds.filter((feedId) => { + const feed = getFeedById(feedId) + return Boolean(feed?.url?.startsWith("rsshub://")) + }).length + }, [allFeeds]) + + // Filter feeds based on selected filter + const filteredFeeds = useMemo(() => { + if (filter === "all") { + return allFeeds + } + return allFeeds.filter((feedId) => { + const feed = getFeedById(feedId) + return Boolean(feed?.url?.startsWith("rsshub://")) + }) + }, [allFeeds, filter]) + + // Clean up selectedFeeds when filter changes + const filteredFeedsSet = useMemo(() => new Set(filteredFeeds), [filteredFeeds]) + + // Clean selected feeds that are not in current filter + useEffect(() => { + setSelectedFeeds((prev) => { + const cleaned = new Set() + prev.forEach((feedId) => { + if (filteredFeedsSet.has(feedId)) { + cleaned.add(feedId) + } + }) + return cleaned + }) + }, [filteredFeedsSet]) const handleSort = useCallback( (field: SortField) => { @@ -91,12 +129,12 @@ const SubscriptionFeedsSection = () => { const handleSelectAll = useCallback( (checked: boolean) => { if (checked) { - setSelectedFeeds(new Set(allFeeds)) + setSelectedFeeds(new Set(filteredFeeds)) } else { setSelectedFeeds(new Set()) } }, - [allFeeds], + [filteredFeeds], ) const handleSelectFeed = useCallback((feedId: string, checked: boolean) => { @@ -111,7 +149,7 @@ const SubscriptionFeedsSection = () => { }) }, []) - const isAllSelected = allFeeds.length > 0 && selectedFeeds.size === allFeeds.length + const isAllSelected = filteredFeeds.length > 0 && selectedFeeds.size === filteredFeeds.length const presentDeleteSubscription = useConfirmUnsubscribeSubscriptionModal() const handleBatchUnsubscribe = useCallback(() => { @@ -121,9 +159,29 @@ const SubscriptionFeedsSection = () => { return (
-

{t("feeds.subscription")}

+
+

{t("feeds.subscription")}

+ {allFeeds.length > 0 && ( + setFilter(value as FeedFilter)} + items={[ + { + label: t("feeds.filter.all", { count: allFeeds.length }), + value: "all", + }, + { + label: t("feeds.filter.rsshub", { count: rsshubFeedsCount }), + value: "rsshub", + }, + ]} + /> + )} +
- {allFeeds.length > 0 && ( + {filteredFeeds.length > 0 && (
{/* Header - Sticky */}
{ {/* Feed List */}