diff --git a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/modal.tsx b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/modal.tsx index d6fff5132..853802c3d 100644 --- a/apps/desktop/layer/renderer/src/components/ui/modal/stacked/modal.tsx +++ b/apps/desktop/layer/renderer/src/components/ui/modal/stacked/modal.tsx @@ -366,7 +366,7 @@ export const ModalInternal = memo(function Modal({ onPointerDownCapture={handleDrag} onPointerDown={relocateModal} > - {!!icon && {icon}} + {!!icon && {icon}} {title} diff --git a/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx b/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx index b8df350d3..86cc6e83d 100644 --- a/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx +++ b/apps/desktop/layer/renderer/src/hooks/biz/useFeedActions.tsx @@ -1,4 +1,3 @@ -import { Button } from "@follow/components/ui/button/index.js" import type { FeedViewType } from "@follow/constants" import { IN_ELECTRON } from "@follow/shared/constants" import { env } from "@follow/shared/env.desktop" @@ -22,10 +21,9 @@ import { useCommandShortcuts } from "~/modules/command/hooks/use-command-binding import { FeedForm } from "~/modules/discover/FeedForm" import { InboxForm } from "~/modules/discover/InboxForm" import { ListForm } from "~/modules/discover/ListForm" -import { - CategoryCreationModalContent, - ListCreationModalContent, -} from "~/modules/settings/tabs/lists/modals" +import { useConfirmUnsubscribeSubscriptionModal } from "~/modules/modal/hooks/useConfirmUnsubscribeSubscriptionModal" +import { useCategoryCreationModal } from "~/modules/settings/tabs/lists/hooks" +import { ListCreationModalContent } from "~/modules/settings/tabs/lists/modals" import { useResetFeed } from "~/queries/feed" import { getFeedById, useFeedById } from "~/store/feed" import { useInboxById } from "~/store/inbox" @@ -41,24 +39,6 @@ import { useNavigateEntry } from "./useNavigateEntry" import { getRouteParams } from "./useRouteParams" import { useBatchUpdateSubscription, useDeleteSubscription } from "./useSubscriptionActions" -const ConfirmDestroyModalContent = ({ onConfirm }: { onConfirm: () => void }) => { - const { t } = useTranslation() - - return ( -
-
- - {t("sidebar.feed_actions.unfollow_feed_many_warning")} -
-
- -
-
- ) -} - export const useFeedActions = ({ feedId, feedIds, @@ -89,6 +69,7 @@ export const useFeedActions = ({ useMemo(() => feedIds || [feedId], [feedId, feedIds]), ) const { present } = useModalStack() + const presentDeleteSubscription = useConfirmUnsubscribeSubscriptionModal() const deleteSubscription = useDeleteSubscription({}) const claimFeed = useFeedClaimModal() @@ -99,6 +80,7 @@ export const useFeedActions = ({ const { mutateAsync: removeFeedFromListMutation } = useRemoveFeedFromFeedList() const { mutateAsync: resetFeed } = useResetFeed() const { mutate: addFeedsToCategoryMutation } = useBatchUpdateSubscription() + const presentCategoryCreationModal = useCategoryCreationModal() const openBoostModal = useBoostModal() const listByView = useOwnedListByView(view!) @@ -239,20 +221,7 @@ export const useFeedActions = ({ label: t("sidebar.feed_column.context_menu.create_category"), icon: , click() { - present({ - title: t("sidebar.feed_column.context_menu.title"), - content: () => ( - { - addFeedsToCategoryMutation({ - feedIdList: isMultipleSelection ? feedIds : [feedId], - category, - view: view!, - }) - }} - /> - ), - }) + presentCategoryCreationModal(view!, isMultipleSelection ? feedIds : [feedId]) }, }), ], @@ -281,17 +250,7 @@ export const useFeedActions = ({ supportMultipleSelection: true, click: () => { if (isMultipleSelection) { - present({ - title: t("sidebar.feed_actions.unfollow_feed_many_confirm"), - content: ({ dismiss }) => ( - { - deleteSubscription.mutate({ feedIdList: feedIds }) - dismiss() - }} - /> - ), - }) + presentDeleteSubscription(feedIds) return } deleteSubscription.mutate({ subscription }) @@ -367,30 +326,32 @@ export const useFeedActions = ({ item.supportMultipleSelection), ) }, [ + addFeedToListMutation, + addFeedsToCategoryMutation, + categories, + claimFeed, + deleteSubscription, feed, + feedId, + feedIds, inbox, - t, - shortcuts, isEntryList, isInMASReview, isInbox, - listByView, - categories, isMultipleSelection, - feedId, - feedIds, - claimFeed, - resetFeed, - openBoostModal, - addFeedToListMutation, - removeFeedFromListMutation, - present, - subscriptions, - subscription, - addFeedsToCategoryMutation, - view, - deleteSubscription, + listByView, navigateEntry, + openBoostModal, + present, + presentCategoryCreationModal, + presentDeleteSubscription, + removeFeedFromListMutation, + resetFeed, + shortcuts, + subscription, + subscriptions, + t, + view, ]) return items diff --git a/apps/desktop/layer/renderer/src/modules/command/commands/global.tsx b/apps/desktop/layer/renderer/src/modules/command/commands/global.tsx index 1e3c290f8..1d2092e6a 100644 --- a/apps/desktop/layer/renderer/src/modules/command/commands/global.tsx +++ b/apps/desktop/layer/renderer/src/modules/command/commands/global.tsx @@ -1,7 +1,7 @@ import { EventBus } from "@follow/utils/event-bus" import { useTranslation } from "react-i18next" -import { useShortcutsModal } from "~/modules/modal/shortcuts" +import { useShortcutsModal } from "~/modules/modal/hooks/useShortcutsModal" import { useRegisterCommandEffect } from "../hooks/use-register-command" import type { Command, CommandCategory } from "../types" diff --git a/apps/desktop/layer/renderer/src/modules/modal/ConfirmDestroyModalContent.tsx b/apps/desktop/layer/renderer/src/modules/modal/ConfirmDestroyModalContent.tsx new file mode 100644 index 000000000..56ccb2da5 --- /dev/null +++ b/apps/desktop/layer/renderer/src/modules/modal/ConfirmDestroyModalContent.tsx @@ -0,0 +1,17 @@ +import { Button } from "@follow/components/ui/button/index.js" +import { useTranslation } from "react-i18next" + +export const ConfirmDestroyModalContent = ({ onConfirm }: { onConfirm: () => void }) => { + const { t } = useTranslation() + + return ( +
+
{t("sidebar.feed_actions.unfollow_feed_many_warning")}
+
+ +
+
+ ) +} diff --git a/apps/desktop/layer/renderer/src/modules/modal/shortcuts.tsx b/apps/desktop/layer/renderer/src/modules/modal/ShortcutModalContent.tsx similarity index 65% rename from apps/desktop/layer/renderer/src/modules/modal/shortcuts.tsx rename to apps/desktop/layer/renderer/src/modules/modal/ShortcutModalContent.tsx index 5774aa6d0..dc23ba96b 100644 --- a/apps/desktop/layer/renderer/src/modules/modal/shortcuts.tsx +++ b/apps/desktop/layer/renderer/src/modules/modal/ShortcutModalContent.tsx @@ -2,16 +2,14 @@ import { MotionButtonBase } from "@follow/components/ui/button/index.js" import { ScrollArea } from "@follow/components/ui/scroll-area/index.js" import { clsx } from "@follow/utils/utils" import { m, useDragControls } from "motion/react" -import { useCallback } from "react" import { useTranslation } from "react-i18next" import { useUISettingKey } from "~/atoms/settings/ui" -import { PlainModal } from "~/components/ui/modal/stacked/custom-modal" -import { useCurrentModal, useModalStack } from "~/components/ui/modal/stacked/hooks" +import { useCurrentModal } from "~/components/ui/modal/stacked/hooks" import { ShortcutsGuideline } from "../command/shortcuts/SettingShortcuts" -const ShortcutModalContent = () => { +export const ShortcutModalContent = () => { const { dismiss } = useCurrentModal() const modalOverlay = useUISettingKey("modalOverlay") const dragControls = useDragControls() @@ -54,28 +52,3 @@ const ShortcutModalContent = () => { ) } - -export const useShortcutsModal = () => { - const { present, dismiss, getModalStackById } = useModalStack() - const id = "shortcuts" - - const showShortcutsModal = useCallback(() => { - present({ - title: "Shortcuts", - id, - overlay: false, - content: () => , - CustomModalComponent: PlainModal, - clickOutsideToDismiss: true, - }) - }, [present]) - - return useCallback(() => { - const shortcutsModal = getModalStackById(id) - if (shortcutsModal && shortcutsModal.modal) { - dismiss(id) - return - } - showShortcutsModal() - }, [dismiss, getModalStackById, showShortcutsModal]) -} diff --git a/apps/desktop/layer/renderer/src/modules/modal/hooks/useConfirmUnsubscribeSubscriptionModal.tsx b/apps/desktop/layer/renderer/src/modules/modal/hooks/useConfirmUnsubscribeSubscriptionModal.tsx new file mode 100644 index 000000000..845862d77 --- /dev/null +++ b/apps/desktop/layer/renderer/src/modules/modal/hooks/useConfirmUnsubscribeSubscriptionModal.tsx @@ -0,0 +1,30 @@ +import { useCallback } from "react" +import { useTranslation } from "react-i18next" + +import { useModalStack } from "~/components/ui/modal/stacked/hooks" +import { useDeleteSubscription } from "~/hooks/biz/useSubscriptionActions" + +import { ConfirmDestroyModalContent } from "../ConfirmDestroyModalContent" + +export const useConfirmUnsubscribeSubscriptionModal = () => { + const { present } = useModalStack() + const deleteSubscription = useDeleteSubscription({}) + const { t } = useTranslation() + return useCallback( + (feedIds: string[]) => { + present({ + title: t("sidebar.feed_actions.unfollow_feed_many_confirm"), + icon: , + content: ({ dismiss }) => ( + { + deleteSubscription.mutate({ feedIdList: feedIds }) + dismiss() + }} + /> + ), + }) + }, + [deleteSubscription, present, t], + ) +} diff --git a/apps/desktop/layer/renderer/src/modules/modal/hooks/useShortcutsModal.tsx b/apps/desktop/layer/renderer/src/modules/modal/hooks/useShortcutsModal.tsx new file mode 100644 index 000000000..c49e3c530 --- /dev/null +++ b/apps/desktop/layer/renderer/src/modules/modal/hooks/useShortcutsModal.tsx @@ -0,0 +1,31 @@ +import { useCallback } from "react" + +import { PlainModal } from "~/components/ui/modal/stacked/custom-modal" +import { useModalStack } from "~/components/ui/modal/stacked/hooks" + +import { ShortcutModalContent } from "../ShortcutModalContent" + +export const useShortcutsModal = () => { + const { present, dismiss, getModalStackById } = useModalStack() + const id = "shortcuts" + + const showShortcutsModal = useCallback(() => { + present({ + title: "Shortcuts", + id, + overlay: false, + content: () => , + CustomModalComponent: PlainModal, + clickOutsideToDismiss: true, + }) + }, [present]) + + return useCallback(() => { + const shortcutsModal = getModalStackById(id) + if (shortcutsModal && shortcutsModal.modal) { + dismiss(id) + return + } + showShortcutsModal() + }, [dismiss, getModalStackById, showShortcutsModal]) +} 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 392303a53..6b877bab4 100644 --- a/apps/desktop/layer/renderer/src/modules/settings/tabs/feeds.tsx +++ b/apps/desktop/layer/renderer/src/modules/settings/tabs/feeds.tsx @@ -1,3 +1,6 @@ +import { Spring } from "@follow/components/constants/spring.js" +import { MotionButtonBase } from "@follow/components/ui/button/index.js" +import { Checkbox } from "@follow/components/ui/checkbox/index.js" import { Divider } from "@follow/components/ui/divider/index.js" import { LoadingCircle } from "@follow/components/ui/loading/index.jsx" import { ScrollArea } from "@follow/components/ui/scroll-area/index.js" @@ -10,27 +13,296 @@ import { TableRow, } from "@follow/components/ui/table/index.jsx" import { EllipsisHorizontalTextWithTooltip } from "@follow/components/ui/typography/index.js" +import { views } from "@follow/constants" +import clsx from "clsx" +import { AnimatePresence, m } from "motion/react" +import type { FC } from "react" +import { memo, useCallback, useMemo, useState } from "react" import { useTranslation } from "react-i18next" +import { useIsInMASReview } from "~/atoms/server-configs" +import { RelativeDay } from "~/components/ui/datetime" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "~/components/ui/dropdown-menu/dropdown-menu" +import { useDialog } from "~/components/ui/modal/stacked/hooks" +import { useBatchUpdateSubscription } from "~/hooks/biz/useSubscriptionActions" import { useAuthQuery } from "~/hooks/common" import { UrlBuilder } from "~/lib/url-builder" import { FeedIcon } from "~/modules/feed/feed-icon" +import { useConfirmUnsubscribeSubscriptionModal } from "~/modules/modal/hooks/useConfirmUnsubscribeSubscriptionModal" import { Balance } from "~/modules/wallet/balance" import { Queries } from "~/queries" +import { useFeedById } from "~/store/feed" +import { useAllFeeds, useSubscriptionByFeedId } from "~/store/subscription" export const SettingFeeds = () => { + const inMas = useIsInMASReview() + return ( +
+ + {!inMas && } +
+ ) +} + +const GRID_COLS_CLASSNAME = tw`grid-cols-[30px_auto_150px_100px]` + +const SubscriptionFeedsSection = () => { + const { t } = useTranslation("settings") + const feedList = useAllFeeds() + const [selectedFeeds, setSelectedFeeds] = useState>(() => new Set()) + + const handleSelectAll = useCallback( + (checked: boolean) => { + if (checked) { + setSelectedFeeds(new Set(feedList.map((feed) => feed.id))) + } else { + setSelectedFeeds(new Set()) + } + }, + [feedList], + ) + + const handleSelectFeed = useCallback((feedId: string, checked: boolean) => { + setSelectedFeeds((prev) => { + const newSet = new Set(prev) + if (checked) { + newSet.add(feedId) + } else { + newSet.delete(feedId) + } + return newSet + }) + }, []) + + const isAllSelected = feedList.length > 0 && selectedFeeds.size === feedList.length + + const presentDeleteSubscription = useConfirmUnsubscribeSubscriptionModal() + const handleBatchUnsubscribe = useCallback(() => { + const feedIds = Array.from(selectedFeeds) + presentDeleteSubscription(feedIds) + }, [presentDeleteSubscription, selectedFeeds]) + + return ( +
+

{t("feeds.subscription")}

+ + {feedList.length > 0 && ( +
+ {/* Header - Sticky */} +
+
+ +
+
{t("feeds.tableHeaders.name")}
+
View
+
Date
+
+ + {/* Feed List */} + {feedList.map((item) => { + return ( + + ) + })} + + {/* Sticky Action Bar at bottom when scrolled */} + + {selectedFeeds.size > 0 && ( + <> + +
+ {selectedFeeds.size} item(s) selected + +
+
+ +
+ {/* + + + + + + + + */} + + + + Move to View + + + + + + + + + Unsubscribe + +
+
+ + )} +
+
+ )} +
+ ) +} + +const ViewSelector: FC<{ selectedFeeds: Set }> = ({ selectedFeeds }) => { + const { t: tCommon } = useTranslation("common") + const { mutate: batchUpdateSubscription } = useBatchUpdateSubscription() + const { ask } = useDialog() + return views.map((view) => { + return ( + { + ask({ + title: "Confirm", + message: `Are you sure you want to move these feeds to ${tCommon(view.name)}?`, + onConfirm: () => { + batchUpdateSubscription({ + feedIdList: Array.from(selectedFeeds), + view: view.view, + }) + }, + }) + }} + > + {tCommon(view.name)} + + ) + }) +} + +const FeedListItem = memo( + ({ + id, + selected, + onSelect, + }: { + id: string + selected: boolean + onSelect: (feedId: string, checked: boolean) => void + }) => { + const subscription = useSubscriptionByFeedId(id) + const feed = useFeedById(id) + const isCustomizeName = subscription?.title && feed?.title !== subscription?.title + const { t: tCommon } = useTranslation("common") + + if (!subscription) return null + + return ( + + ) + }, +) + +const FeedClaimedSection = () => { const { t } = useTranslation("settings") const claimedList = useAuthQuery(Queries.feed.claimedList()) + const numberFormatter = useMemo(() => new Intl.NumberFormat("en-US", {}), []) + return (
+
+

{t("feeds.claim")}

+

{t("feeds.claimTips")}

- - {claimedList.data?.length ? ( + {claimedList.isLoading ? ( + + ) : !claimedList.data?.length ? ( +
+

{t("feeds.noFeeds")}

+
+ ) : null} + {claimedList.data?.length ? ( + @@ -61,7 +333,7 @@ export const SettingFeeds = () => { - {row.subscriptionCount} + {numberFormatter.format(row.subscriptionCount)} {BigInt(row.tipAmount || 0n)} @@ -70,14 +342,8 @@ export const SettingFeeds = () => { ))}
- ) : claimedList.isLoading ? ( - - ) : ( -
-

{t("feeds.noFeeds")}

-
- )} -
+
+ ) : null}
) diff --git a/apps/desktop/layer/renderer/src/modules/settings/tabs/lists/hooks.tsx b/apps/desktop/layer/renderer/src/modules/settings/tabs/lists/hooks.tsx new file mode 100644 index 000000000..b4dfcb012 --- /dev/null +++ b/apps/desktop/layer/renderer/src/modules/settings/tabs/lists/hooks.tsx @@ -0,0 +1,32 @@ +import type { FeedViewType } from "@follow/constants" +import { useCallback } from "react" +import { useTranslation } from "react-i18next" + +import { useModalStack } from "~/components/ui/modal/stacked/hooks" +import { useBatchUpdateSubscription } from "~/hooks/biz/useSubscriptionActions" + +import { CategoryCreationModalContent } from "./modals" + +export const useCategoryCreationModal = () => { + const { t } = useTranslation() + const { present } = useModalStack() + const { mutate: addFeedsToCategoryMutation } = useBatchUpdateSubscription() + return useCallback( + (view: FeedViewType, feedIds: string[]) => + present({ + title: t("sidebar.feed_column.context_menu.title"), + content: () => ( + { + addFeedsToCategoryMutation({ + feedIdList: feedIds, + category, + view: view!, + }) + }} + /> + ), + }), + [addFeedsToCategoryMutation, present, t], + ) +} diff --git a/apps/desktop/layer/renderer/src/pages/settings/(settings)/feeds.tsx b/apps/desktop/layer/renderer/src/pages/settings/(settings)/feeds.tsx index 229c34a70..6467cb1c7 100644 --- a/apps/desktop/layer/renderer/src/pages/settings/(settings)/feeds.tsx +++ b/apps/desktop/layer/renderer/src/pages/settings/(settings)/feeds.tsx @@ -9,7 +9,6 @@ export const loader = defineSettingPageData({ icon: iconName, name: "titles.feeds", priority, - hideIf: (ctx) => ctx.isInMASReview, }) export function Component() { diff --git a/apps/desktop/layer/renderer/src/store/subscription/hooks.ts b/apps/desktop/layer/renderer/src/store/subscription/hooks.ts index 9db7d079c..634c12706 100644 --- a/apps/desktop/layer/renderer/src/store/subscription/hooks.ts +++ b/apps/desktop/layer/renderer/src/store/subscription/hooks.ts @@ -1,10 +1,11 @@ -import { FeedViewType, viewList } from "@follow/constants" +import type { FeedViewType } from "@follow/constants" import { useCallback, useMemo } from "react" import { useGeneralSettingSelector } from "~/atoms/settings/general" import { useFeedStore } from "../feed" import { + categoriesSelector, categoryOpenStateByViewSelector, feedIdByViewSelector, feedSubscriptionCountSelector, @@ -15,7 +16,10 @@ import { subscriptionByFeedIdSelector, subscriptionByViewSelector, subscriptionCategoryExistSelector, + subscriptionInboxSelector, + subscriptionListSelector, subscriptionsByFeedIsdSelector, + viewWithSubscriptionSelector, } from "./selector" import { useSubscriptionStore } from "./store" @@ -34,26 +38,9 @@ export const useSubscriptionByView = (view: FeedViewType) => useCallback((state) => subscriptionByViewSelector(view)(state) || [], [view]), ) -export const useViewWithSubscription = () => - useSubscriptionStore( - useCallback((state) => { - return viewList.filter((view) => { - if ( - view === FeedViewType.Articles || - view === FeedViewType.SocialMedia || - view === FeedViewType.Pictures || - view === FeedViewType.Videos - ) { - return true - } else { - return state.feedIdByView[view].length > 0 - } - }) - }, []), - ) +export const useViewWithSubscription = () => useSubscriptionStore(viewWithSubscriptionSelector) -export const useCategories = () => - useSubscriptionStore(useCallback((state) => state.categories || [], [])) +export const useCategories = () => useSubscriptionStore(categoriesSelector) export const useCategoriesByView = (view: FeedViewType) => useSubscriptionStore( @@ -129,21 +116,11 @@ export const useAllFeeds = () => { } export const useAllLists = () => { - return useSubscriptionStore( - useCallback( - (store) => Object.values(store.data).filter((subscription) => subscription.listId), - [], - ), - ) + return useSubscriptionStore(subscriptionListSelector) } export const useAllInboxes = () => { - return useSubscriptionStore( - useCallback( - (store) => Object.values(store.data).filter((subscription) => subscription.inboxId), - [], - ), - ) + return useSubscriptionStore(subscriptionInboxSelector) } export const useFeedsGroupedData = (view: FeedViewType) => { diff --git a/apps/desktop/layer/renderer/src/store/subscription/selector.ts b/apps/desktop/layer/renderer/src/store/subscription/selector.ts index 2d3243203..1bfbc7ee7 100644 --- a/apps/desktop/layer/renderer/src/store/subscription/selector.ts +++ b/apps/desktop/layer/renderer/src/store/subscription/selector.ts @@ -1,4 +1,4 @@ -import type { FeedViewType } from "@follow/constants" +import { FeedViewType, viewList } from "@follow/constants" import { FEED_COLLECTION_LIST, ROUTE_FEED_IN_FOLDER } from "~/constants" @@ -56,3 +56,26 @@ export const listSubscriptionCountSelector = (state: State) => state.listIds.siz export const inboxSubscriptionCountSelector = (state: State) => state.inboxIds.size export const isSubscribedSelector = (feedId: string) => (state: State) => !!state.data[feedId] + +export const subscriptionListSelector = (state: State) => + Object.values(state.data).filter((subscription) => subscription.listId) + +export const subscriptionInboxSelector = (store: State) => + Object.values(store.data).filter((subscription) => subscription.inboxId) + +export const viewWithSubscriptionSelector = (state: State) => { + return viewList.filter((view) => { + if ( + view === FeedViewType.Articles || + view === FeedViewType.SocialMedia || + view === FeedViewType.Pictures || + view === FeedViewType.Videos + ) { + return true + } else { + return state.feedIdByView[view].length > 0 + } + }) +} + +export const categoriesSelector = (state: State) => state.categories || [] diff --git a/locales/app/en.json b/locales/app/en.json index ed73585ec..ff90e93d1 100644 --- a/locales/app/en.json +++ b/locales/app/en.json @@ -356,7 +356,7 @@ "sidebar.feed_actions.resetting_feed": "Resetting feed...", "sidebar.feed_actions.unfollow": "Unfollow", "sidebar.feed_actions.unfollow_feed": "Unfollow Feed", - "sidebar.feed_actions.unfollow_feed_many": "Unfollow all selected feeds", + "sidebar.feed_actions.unfollow_feed_many": "Unfollow All Selected Feeds", "sidebar.feed_actions.unfollow_feed_many_confirm": "Confirm unfollow all selected feeds?", "sidebar.feed_actions.unfollow_feed_many_warning": "Warning: This operation will unfollow all selected feeds, and cannot be undone.", "sidebar.feed_column.context_menu.add_feeds_to_category": "Move to Category", diff --git a/locales/settings/en.json b/locales/settings/en.json index c2ec5de01..39267e538 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -145,8 +145,10 @@ "discoverFilters.filters": "Filters", "discoverFilters.language": "Language", "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.noFeeds": "No claimed feeds", + "feeds.subscription": "Subscribed Feeds", "feeds.tableHeaders.name": "Name", "feeds.tableHeaders.subscriptionCount": "Subs", "feeds.tableHeaders.tipAmount": "Tips",