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:
parent
f7460fd7ce
commit
519d0ba110
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": "購読日",
|
||||
|
|
|
|||
|
|
@ -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": "订阅日期",
|
||||
|
|
|
|||
|
|
@ -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": "訂閱日期",
|
||||
|
|
|
|||
Loading…
Reference in New Issue