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 <tukon479@gmail.com>
This commit is contained in:
Innei 2025-11-26 17:35:24 +08:00
parent f7460fd7ce
commit 519d0ba110
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
5 changed files with 73 additions and 7 deletions

View File

@ -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<Set<string>>(() => new Set())
const [sortField, setSortField] = useState<SortField>("name")
const [sortDirection, setSortDirection] = useState<SortDirection>("asc")
const [filter, setFilter] = useState<FeedFilter>("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<string>()
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 (
<section className="relative mt-4">
<h2 className="mb-2 text-lg font-semibold">{t("feeds.subscription")}</h2>
<div className="mb-2 flex items-center justify-between gap-4">
<h2 className="text-lg font-semibold">{t("feeds.subscription")}</h2>
{allFeeds.length > 0 && (
<ResponsiveSelect
size="sm"
triggerClassName="w-36"
value={filter}
onValueChange={(value) => setFilter(value as FeedFilter)}
items={[
{
label: t("feeds.filter.all", { count: allFeeds.length }),
value: "all",
},
{
label: t("feeds.filter.rsshub", { count: rsshubFeedsCount }),
value: "rsshub",
},
]}
/>
)}
</div>
{allFeeds.length > 0 && (
{filteredFeeds.length > 0 && (
<div className="mt-6 space-y-0.5">
{/* Header - Sticky */}
<div
@ -190,7 +248,7 @@ const SubscriptionFeedsSection = () => {
{/* Feed List */}
<div className="relative">
<SortedFeedsList
feeds={allFeeds}
feeds={filteredFeeds}
sortField={sortField}
sortDirection={sortDirection}
selectedFeeds={selectedFeeds}

View File

@ -240,6 +240,8 @@
"discoverFilters.title": "Discover Filters",
"feeds.claim": "Claim Feeds",
"feeds.claimTips": "To claim your feeds and receive tips, right-click on the feed in your subscription list and select Claim.",
"feeds.filter.all": "All ({{count}})",
"feeds.filter.rsshub": "RSSHub ({{count}})",
"feeds.noFeeds": "No claimed feeds",
"feeds.subscription": "Subscribed Feeds",
"feeds.tableHeaders.date": "Subscribed Date",

View File

@ -236,6 +236,8 @@
"discoverFilters.title": "発見フィルタ",
"feeds.claim": "フィードを認証",
"feeds.claimTips": "フィードを認証してチップを受け取るには、購読リストのフィードを右クリックして「フィードをクレーム」を選択してください。",
"feeds.filter.all": "すべて ({{count}})",
"feeds.filter.rsshub": "RSSHub ({{count}})",
"feeds.noFeeds": "認証されたフィードはありません",
"feeds.subscription": "購読済みフィード",
"feeds.tableHeaders.date": "購読日",

View File

@ -235,6 +235,8 @@
"discoverFilters.title": "发现筛选",
"feeds.claim": "认证订阅源",
"feeds.claimTips": "要认证你的订阅源并接收打赏,请在订阅列表中右键点击订阅源并选择「认证」。",
"feeds.filter.all": "全部 ({{count}})",
"feeds.filter.rsshub": "RSSHub ({{count}})",
"feeds.noFeeds": "没有已认证的订阅源",
"feeds.subscription": "已订阅的订阅源",
"feeds.tableHeaders.date": "订阅日期",

View File

@ -240,6 +240,8 @@
"discoverFilters.title": "發現篩選",
"feeds.claim": "認領 RSS 摘要",
"feeds.claimTips": "要認領您的 RSS 摘要並接收贊助,請在您的訂閱列表中右鍵點擊該 RSS 摘要,然後選擇「認領」。",
"feeds.filter.all": "全部 ({{count}})",
"feeds.filter.rsshub": "RSSHub ({{count}})",
"feeds.noFeeds": "沒有已認領的 RSS 摘要",
"feeds.subscription": "已訂閱的 RSS 摘要",
"feeds.tableHeaders.date": "訂閱日期",