Skip to content

Commit

Permalink
feat: muti select action (RSSNext#1062)
Browse files Browse the repository at this point in the history
Co-authored-by: hyoban <hyoban@users.noreply.github.com>
  • Loading branch information
hyoban and hyoban authored Oct 24, 2024
1 parent 079ec27 commit 94ab663
Show file tree
Hide file tree
Showing 13 changed files with 415 additions and 68 deletions.
1 change: 1 addition & 0 deletions apps/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"react-intersection-observer": "9.13.1",
"react-resizable-layout": "npm:@innei/react-resizable-layout@0.7.3-fork.1",
"react-router-dom": "6.27.0",
"react-selecto": "^1.26.3",
"react-shadow": "20.5.0",
"react-virtuoso": "4.12.0",
"rehype-infer-description-meta": "2.0.0",
Expand Down
69 changes: 63 additions & 6 deletions apps/renderer/src/hooks/biz/useFeedActions.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
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"
Expand Down Expand Up @@ -29,12 +30,32 @@ import { useNavigateEntry } from "./useNavigateEntry"
import { getRouteParams } from "./useRouteParams"
import { useDeleteSubscription } from "./useSubscriptionActions"

const ConfirmDestroyModalContent = ({ onConfirm }: { onConfirm: () => void }) => {
const { t } = useTranslation()

return (
<div className="w-[540px]">
<div className="mb-4">
<i className="i-mingcute-warning-fill -mb-1 mr-1 size-5 text-red-500" />
{t("sidebar.feed_actions.unfollow_feed_many_warning")}
</div>
<div className="flex justify-end">
<Button className="bg-red-600" onClick={onConfirm}>
{t("words.confirm")}
</Button>
</div>
</div>
)
}

export const useFeedActions = ({
feedId,
feedIds,
view,
type,
}: {
feedId: string
feedIds?: string[]
view?: number
type?: "feedList" | "entryList"
}) => {
Expand All @@ -57,6 +78,8 @@ export const useFeedActions = ({

const listByView = useOwnedList(view!)

const isMultipleSelection = feedIds && feedIds.length > 0

const items = useMemo(() => {
if (!feed) return []

Expand All @@ -66,7 +89,11 @@ export const useFeedActions = ({
label: t("sidebar.feed_actions.mark_all_as_read"),
shortcut: "Meta+Shift+A",
disabled: isEntryList,
click: () => subscriptionActions.markReadByFeedIds({ feedIds: [feedId] }),
click: () =>
subscriptionActions.markReadByFeedIds({
feedIds: isMultipleSelection ? feedIds : [feedId],
}),
supportMultipleSelection: true,
},
!feed.ownerUserId &&
!!isBizId(feed.id) &&
Expand Down Expand Up @@ -103,6 +130,7 @@ export const useFeedActions = ({
type: "text" as const,
label: t("sidebar.feed_column.context_menu.add_feeds_to_list"),
disabled: isInbox,
supportMultipleSelection: true,
submenu: [
...listByView.map((list) => {
const isIncluded = list.feedIds.includes(feedId)
Expand All @@ -111,6 +139,14 @@ export const useFeedActions = ({
type: "text" as const,
checked: isIncluded,
click() {
if (isMultipleSelection) {
addFeedToListMutation({
feedIds,
listId: list.id,
})
return
}

if (!isIncluded) {
addFeedToListMutation({
feedId,
Expand Down Expand Up @@ -157,12 +193,31 @@ export const useFeedActions = ({
},
{
type: "text" as const,
label: isEntryList
? t("sidebar.feed_actions.unfollow_feed")
: t("sidebar.feed_actions.unfollow"),
label: isMultipleSelection
? t("sidebar.feed_actions.unfollow_feed_many")
: isEntryList
? t("sidebar.feed_actions.unfollow_feed")
: t("sidebar.feed_actions.unfollow"),
shortcut: "Meta+Backspace",
disabled: isInbox,
click: () => deleteSubscription.mutate(subscription),
supportMultipleSelection: true,
click: () => {
if (isMultipleSelection) {
present({
title: t("sidebar.feed_actions.unfollow_feed_many_confirm"),
content: ({ dismiss }) => (
<ConfirmDestroyModalContent
onConfirm={() => {
deleteSubscription.mutate({ feedIdList: feedIds })
dismiss()
}}
/>
),
})
return
}
deleteSubscription.mutate({ subscription })
},
},
{
type: "text" as const,
Expand Down Expand Up @@ -235,6 +290,8 @@ export const useFeedActions = ({
isInbox,
listByView,
feedId,
isMultipleSelection,
feedIds,
claimFeed,
openBoostModal,
addFeedToListMutation,
Expand Down Expand Up @@ -287,7 +344,7 @@ export const useListActions = ({ listId, view }: { listId: string; view: FeedVie
type: "text" as const,
label: t("sidebar.feed_actions.unfollow"),
shortcut: "Meta+Backspace",
click: () => deleteSubscription(subscription),
click: () => deleteSubscription({ subscription }),
},
{
type: "text" as const,
Expand Down
21 changes: 18 additions & 3 deletions apps/renderer/src/hooks/biz/useSubscriptionActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,21 @@ export const useDeleteSubscription = ({ onSuccess }: { onSuccess?: () => void })
const { t } = useTranslation()

return useMutation({
mutationFn: async (subscription: SubscriptionFlatModel) =>
mutationFn: async ({
subscription,
feedIdList,
}: {
subscription?: SubscriptionFlatModel
feedIdList?: string[]
}) => {
if (feedIdList) {
await subscriptionActions.unfollowMany(feedIdList)
toast.success(t("notify.unfollow_feed_many"))
return
}

if (!subscription) return

subscriptionActions.unfollow(subscription.feedId).then((feed) => {
subscriptionQuery.byView(subscription.view).invalidate()
feedUnreadActions.updateByFeedId(subscription.feedId, 0)
Expand Down Expand Up @@ -57,13 +71,14 @@ export const useDeleteSubscription = ({ onSuccess }: { onSuccess?: () => void })
onClick: undo,
},
})
}),
})
},

onSuccess: (_) => {
onSuccess?.()
},
onMutate(variables) {
if (getRouteParams().feedId === variables.feedId) {
if (getRouteParams().feedId === variables.subscription?.feedId) {
navigateEntry({
feedId: null,
entryId: null,
Expand Down
1 change: 1 addition & 0 deletions apps/renderer/src/lib/native-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type BaseMenuItemText = MenuItemWithHide<{
shortcut?: string
disabled?: boolean
checked?: boolean
supportMultipleSelection?: boolean
}>

type BaseMenuItemSeparator = MenuItemWithHide<{
Expand Down
11 changes: 11 additions & 0 deletions apps/renderer/src/modules/feed-column/atom.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getStorageNS } from "@follow/utils/ns"
import { atom } from "jotai"
import { atomWithStorage } from "jotai/utils"

import { createAtomHooks } from "~/lib/jotai"
Expand Down Expand Up @@ -33,3 +34,13 @@ export const setFeedListSortOrder = (order: FeedListSortOrder) => {
order,
})
}

export const [
,
useSelectedFeedIds,
,
useSetSelectedFeedIds,
getSelectedFeedIds,
setSelectedFeedIds,
useSelectedFeedIdsSelector,
] = createAtomHooks(atom<string[]>([]))
4 changes: 2 additions & 2 deletions apps/renderer/src/modules/feed-column/category.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ function FeedCategoryImpl({ data: ids, view, categoryOpenStateData }: FeedCatego
<div
data-active={isActive || isContextMenuOpen}
className={cn(
"flex w-full cursor-menu items-center justify-between rounded-md px-2.5",
"my-px flex w-full cursor-menu items-center justify-between rounded-md px-2.5",
feedColumnStyles.item,
)}
onClick={(e) => {
Expand Down Expand Up @@ -303,7 +303,7 @@ function FeedCategoryImpl({ data: ids, view, categoryOpenStateData }: FeedCatego
{open && (
<m.div
ref={itemsRef}
className="overflow-hidden"
className="space-y-px overflow-hidden"
initial={
!!showCollapse && {
height: 0,
Expand Down
39 changes: 32 additions & 7 deletions apps/renderer/src/modules/feed-column/item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { subscriptionActions, useSubscriptionByFeedId } from "~/store/subscripti
import { useFeedUnreadStore } from "~/store/unread"

import { BoostCertification } from "../boost/boost-certification"
import { useSelectedFeedIds } from "./atom"
import { feedColumnStyles } from "./styles"
import { UnreadNumber } from "./unread-number"

Expand All @@ -43,9 +44,16 @@ const FeedItemImpl = ({ view, feedId, className }: FeedItemProps) => {
const subscription = useSubscriptionByFeedId(feedId)
const navigate = useNavigateEntry()
const feed = useFeedById(feedId)
const [selectedFeedIds, setSelectedFeedIds] = useSelectedFeedIds()

const handleNavigate: React.MouseEventHandler<HTMLDivElement> = useCallback(
const handleClick: React.MouseEventHandler<HTMLDivElement> = useCallback(
(e) => {
if (e.metaKey) {
return
} else {
setSelectedFeedIds([])
}

e.stopPropagation()
if (view === undefined) return
navigate({
Expand All @@ -58,18 +66,25 @@ const FeedItemImpl = ({ view, feedId, className }: FeedItemProps) => {
getMainContainerElement()?.focus()
})
},
[feedId, navigate, view, feed?.type],
[feedId, navigate, setSelectedFeedIds, view],
)

const feedUnread = useFeedUnreadStore((state) => state.data[feedId] || 0)

const isActive = useRouteParamsSelector((routerParams) => routerParams.feedId === feedId)

const { items } = useFeedActions({ feedId, view })
const { items } = useFeedActions({
feedIds: selectedFeedIds,
feedId,
view,
})

const [isContextMenuOpen, setIsContextMenuOpen] = useState(false)
useAnyPointDown(() => {
useAnyPointDown((e) => {
setIsContextMenuOpen(false)
if (!(e.target instanceof HTMLElement) || !e.target.closest("[data-feed-id]")) {
setSelectedFeedIds([])
}
})
if (!feed) return null

Expand All @@ -79,14 +94,14 @@ const FeedItemImpl = ({ view, feedId, className }: FeedItemProps) => {
<>
<div
data-feed-id={feedId}
data-active={isActive || isContextMenuOpen}
data-active={isActive || isContextMenuOpen || selectedFeedIds.includes(feedId)}
className={cn(
"flex w-full cursor-menu items-center justify-between rounded-md py-[2px] pr-2.5 text-sm font-medium leading-loose",
feedColumnStyles.item,
isFeed ? "py-[2px]" : "py-1.5",
className,
)}
onClick={handleNavigate}
onClick={handleClick}
onDoubleClick={() => {
window.open(UrlBuilder.shareFeed(feedId, view), "_blank")
}}
Expand Down Expand Up @@ -116,7 +131,17 @@ const FeedItemImpl = ({ view, feedId, className }: FeedItemProps) => {
},
)
}
showNativeMenu(nextItems, e)
showNativeMenu(
nextItems.filter(
(item) =>
selectedFeedIds.length === 0 ||
(typeof item === "object" &&
item !== null &&
"supportMultipleSelection" in item &&
item.supportMultipleSelection),
),
e,
)
}}
>
<div
Expand Down
Loading

0 comments on commit 94ab663

Please sign in to comment.