From 0e78ff6f6ca8d24492956fe1ec4122ea3b264ff1 Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 14 Jun 2024 17:31:05 +0800 Subject: [PATCH 1/6] feat: remove modal Signed-off-by: Innei --- .vscode/settings.json | 4 + .../feed-column/category-remove-dialog.tsx | 65 ++++---- .../feed-column/category-rename-dialog.tsx | 53 +++--- .../src/components/feed-column/category.tsx | 156 ++++++++---------- .../src/components/feed-column/list.tsx | 2 + .../src/components/ui/alert-dialog.tsx | 139 ---------------- .../src/components/ui/button/index.tsx | 66 +++++--- .../src/components/ui/button/variants.tsx | 27 ++- src/renderer/src/components/ui/command.tsx | 148 ----------------- .../src/components/ui/dialog/dialog.tsx | 124 -------------- .../src/components/ui/dialog/index.ts | 1 - .../src/components/ui/modal/stacked/modal.tsx | 24 ++- src/renderer/src/store/subscription.ts | 30 +++- 13 files changed, 245 insertions(+), 594 deletions(-) delete mode 100644 src/renderer/src/components/ui/alert-dialog.tsx delete mode 100644 src/renderer/src/components/ui/command.tsx delete mode 100644 src/renderer/src/components/ui/dialog/dialog.tsx delete mode 100644 src/renderer/src/components/ui/dialog/index.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index ab0ea6d91c..e65907228e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,10 @@ } }, + "tailwindCSS.experimental.classRegex": [ + ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], + ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] + ], // You may don't need this in the future "eslint.experimental.useFlatConfig": true, "eslint.validate": [ diff --git a/src/renderer/src/components/feed-column/category-remove-dialog.tsx b/src/renderer/src/components/feed-column/category-remove-dialog.tsx index f95c521796..660a908cf3 100644 --- a/src/renderer/src/components/feed-column/category-remove-dialog.tsx +++ b/src/renderer/src/components/feed-column/category-remove-dialog.tsx @@ -1,63 +1,56 @@ -import { - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@renderer/components/ui/alert-dialog" import { apiClient } from "@renderer/lib/api-fetch" import { Queries } from "@renderer/queries" +import { subscriptionActions } from "@renderer/store" import { useMutation } from "@tanstack/react-query" -export function CategoryRemoveDialog({ +import { StyledButton } from "../ui/button" +import { useCurrentModal } from "../ui/modal" + +export function CategoryRemoveDialogContent({ feedIdList, - onSuccess, - category, + view, }: { feedIdList: string[] onSuccess?: () => void - category: string + view?: number }) { - const renameMutation = useMutation({ + const deleteMutation = useMutation({ mutationFn: async () => - apiClient.categories.$delete({ json: { feedIdList, deleteSubscriptions: false, }, }), + onSuccess: () => { Queries.subscription.byView(view).invalidate() - - onSuccess?.() + subscriptionActions.deleteCategory(feedIdList) }, }) + const { dismiss } = useCurrentModal() + return ( - - - - Remove category - {" "} - {category} - ? - - - This operation will delete your category, but the feeds it contains - will be retained and grouped by website. - - - - Cancel - renameMutation.mutate()}> +
+

+ This operation will delete your category, but the feeds it contains will + be retained and grouped by website. +

+ +
+ + Cancel + + deleteMutation.mutateAsync().then(() => dismiss())} + > Continue - - - + +
+
) } diff --git a/src/renderer/src/components/feed-column/category-rename-dialog.tsx b/src/renderer/src/components/feed-column/category-rename-dialog.tsx index e1b7c028ef..649b8b4669 100644 --- a/src/renderer/src/components/feed-column/category-rename-dialog.tsx +++ b/src/renderer/src/components/feed-column/category-rename-dialog.tsx @@ -1,10 +1,5 @@ import { zodResolver } from "@hookform/resolvers/zod" -import { Button } from "@renderer/components/ui/button" -import { - DialogContent, - DialogHeader, - DialogTitle, -} from "@renderer/components/ui/dialog" +import { StyledButton } from "@renderer/components/ui/button" import { Form, FormControl, @@ -23,7 +18,7 @@ const formSchema = z.object({ category: z.string(), }) -export function CategoryRenameDialog({ +export function CategoryRenameContent({ feedIdList, onSuccess, category, @@ -43,7 +38,6 @@ export function CategoryRenameDialog({ const renameMutation = useMutation({ mutationFn: async (values: z.infer) => - apiClient.categories.$patch({ json: { feedIdList, @@ -62,29 +56,26 @@ export function CategoryRenameDialog({ } return ( - - - Rename Category - -
- - ( - - - - - - - )} - /> - - - -
+ + + + ) } diff --git a/src/renderer/src/components/feed-column/category.tsx b/src/renderer/src/components/feed-column/category.tsx index 91df4a5fb5..47c3ad0706 100644 --- a/src/renderer/src/components/feed-column/category.tsx +++ b/src/renderer/src/components/feed-column/category.tsx @@ -2,24 +2,23 @@ import { Collapsible, CollapsibleTrigger, } from "@renderer/components/ui/collapsible" -import { Dialog } from "@renderer/components/ui/dialog" -import { apiClient } from "@renderer/lib/api-fetch" -import { client } from "@renderer/lib/client" import { levels } from "@renderer/lib/constants" import { showNativeMenu } from "@renderer/lib/native-menu" import { cn } from "@renderer/lib/utils" import type { FeedListModel } from "@renderer/models" -import { Queries } from "@renderer/queries" import { feedActions, useFeedActiveList, useUnreadStore, } from "@renderer/store" -import { useMutation } from "@tanstack/react-query" import { AnimatePresence, m } from "framer-motion" import { useEffect, useState } from "react" -import { CategoryRenameDialog } from "./category-rename-dialog" +import { useModalStack } from "../ui/modal/stacked/hooks" +import { CategoryRemoveDialogContent } from "./category-remove-dialog" +import { + CategoryRenameContent, +} from "./category-rename-dialog" import { FeedItem } from "./item" const { setActiveList } = feedActions @@ -36,22 +35,8 @@ export function FeedCategory({ const activeList = useFeedActiveList() const [open, setOpen] = useState(!data.name) - const [dialogOpen, setDialogOpen] = useState(false) const feedIdList = data.list.map((feed) => feed.feedId) - const deleteMutation = useMutation({ - mutationFn: async () => - apiClient.categories.$delete({ - json: { - feedIdList, - deleteSubscriptions: false, - }, - }), - - onSuccess: () => { - Queries.subscription.byView(view).invalidate() - }, - }) useEffect(() => { if (data.name) { @@ -74,7 +59,12 @@ export function FeedCategory({ data.list.reduce((acc, cur) => (state.data[cur.feedId] || 0) + acc, 0), ) - const sortByUnreadFeedList = useUnreadStore((state) => data.list.sort((a, b) => (state.data[b.feedId] || 0) - (state.data[a.feedId] || 0))) + const sortByUnreadFeedList = useUnreadStore((state) => + data.list.sort( + (a, b) => (state.data[b.feedId] || 0) - (state.data[a.feedId] || 0), + ), + ) + const { present } = useModalStack() return ( e.stopPropagation()} > {!!data.name && ( - -
{ - e.stopPropagation() - setCategoryActive() - }} - onContextMenu={(e) => { - showNativeMenu( - [ - { - type: "text", - label: "Rename Category", - click: () => setDialogOpen(true), +
{ + e.stopPropagation() + setCategoryActive() + }} + onContextMenu={(e) => { + showNativeMenu( + [ + { + type: "text", + label: "Rename Category", + click: () => { + present({ + title: "Rename Category", + content: ({ dismiss }) => ( + + ), + }) }, - { - type: "text", - label: "Delete Category", + }, + { + type: "text", + label: "Delete Category", - click: async () => { - const result = await client?.showConfirmDialog({ - title: `Delete category ${data.name}?`, - message: `This operation will delete your category, but the feeds it contains will be retained and grouped by website.`, - options: { - buttons: ["Delete", "Cancel"], - }, - }) - if (result) { - deleteMutation.mutate() - } - }, + click: async () => { + present({ + title: `Delete category ${data.name}?`, + content: () => ( + + ), + }) }, - ], - e, - ) - }} - > -
- - - {!setActiveList && ( - {data.name} - )} - - {!!setActiveList && {data.name}} -
- {!!unread && ( -
{unread}
- )} - setDialogOpen(false)} - /> + }, + ], + e, + ) + }} + > +
+ + + {!setActiveList && {data.name}} + + {!!setActiveList && {data.name}}
-
+ {!!unread && ( +
{unread}
+ )} + )} {open && ( diff --git a/src/renderer/src/components/feed-column/list.tsx b/src/renderer/src/components/feed-column/list.tsx index 8a25969cd5..e8e7e9a722 100644 --- a/src/renderer/src/components/feed-column/list.tsx +++ b/src/renderer/src/components/feed-column/list.tsx @@ -52,6 +52,8 @@ const useData = (view: FeedViewType) => { for (const subscription of subscriptions) { if (!subscription.category) { if (subscription.feeds.siteUrl) { + // FIXME @DIYgod + // The logic here makes it impossible to remove the auto-generated category based on domain const { domain } = parse(subscription.feeds.siteUrl) if (domain && domains[domain] > 1) { subscription.category = diff --git a/src/renderer/src/components/ui/alert-dialog.tsx b/src/renderer/src/components/ui/alert-dialog.tsx deleted file mode 100644 index b46b8badf9..0000000000 --- a/src/renderer/src/components/ui/alert-dialog.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" -import { cn } from "@renderer/lib/utils" -import * as React from "react" - -import { buttonVariants } from "./button/variants" - -const AlertDialog = AlertDialogPrimitive.Root - -const AlertDialogTrigger = AlertDialogPrimitive.Trigger - -const AlertDialogPortal = AlertDialogPrimitive.Portal - -const AlertDialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName - -const AlertDialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - -)) -AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName - -const AlertDialogHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -AlertDialogHeader.displayName = "AlertDialogHeader" - -const AlertDialogFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -AlertDialogFooter.displayName = "AlertDialogFooter" - -const AlertDialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName - -const AlertDialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AlertDialogDescription.displayName = - AlertDialogPrimitive.Description.displayName - -const AlertDialogAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName - -const AlertDialogCancel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName - -export { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogOverlay, - AlertDialogPortal, - AlertDialogTitle, - AlertDialogTrigger, -} diff --git a/src/renderer/src/components/ui/button/index.tsx b/src/renderer/src/components/ui/button/index.tsx index 9cd2dad712..cba758e4cc 100644 --- a/src/renderer/src/components/ui/button/index.tsx +++ b/src/renderer/src/components/ui/button/index.tsx @@ -6,14 +6,18 @@ import type { HTMLMotionProps } from "framer-motion" import { m } from "framer-motion" import * as React from "react" +import { LoadingCircle } from "../loading" import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip" import { buttonVariants, styledButtonVariant } from "./variants" +export interface BaseButtonProps { + isLoading?: boolean +} export interface ButtonProps extends React.ButtonHTMLAttributes, - VariantProps { + VariantProps, + BaseButtonProps { asChild?: boolean - isLoading?: boolean } export const Button = React.forwardRef( @@ -22,14 +26,13 @@ export const Button = React.forwardRef( ref, ) => { const Comp = asChild ? Slot : "button" - if (isLoading) { - props.disabled = true - } + return ( {isLoading && ( @@ -106,18 +109,43 @@ MotionButtonBase.displayName = "MotionButtonBase" export const StyledButton = React.forwardRef< HTMLButtonElement, - HTMLMotionProps<"button"> ->(({ className, ...props }, ref) => ( - , "children"> & + BaseButtonProps & + VariantProps > - {props.children} - -)) +>(({ className, isLoading, variant, status, ...props }, ref) => { + const handleClick: React.MouseEventHandler = + React.useCallback( + (e) => { + if (isLoading || props.disabled) { + e.preventDefault() + return + } + + props.onClick?.(e) + }, + [isLoading, props], + ) + return ( + + + {isLoading && ( + + + + )} + {props.children} + + + ) +}) diff --git a/src/renderer/src/components/ui/button/variants.tsx b/src/renderer/src/components/ui/button/variants.tsx index 1406de9a7f..d88a5dc955 100644 --- a/src/renderer/src/components/ui/button/variants.tsx +++ b/src/renderer/src/components/ui/button/variants.tsx @@ -31,19 +31,42 @@ export const buttonVariants = cva( }, ) export const styledButtonVariant = cva( - "inline-flex select-none cursor-default items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition active:transition-none", + "inline-flex select-none disabled:cursor-not-allowed cursor-default items-center gap-2 justify-center rounded-lg py-2 px-3 text-sm outline-offset-2 transition active:transition-none", { + compoundVariants: [ + { + variant: "primary", + status: "disabled", + className: "text-zinc-50 bg-theme-accent/30", + }, + { + variant: "plain", + status: "disabled", + className: "text-primary-foreground/50", + }, + ], variants: { + status: { + disabled: "cursor-not-allowed", + }, variant: { primary: cn( "bg-theme-accent text-zinc-100", "hover:contrast-[1.10] active:contrast-125", "font-semibold", - "disabled:cursor-not-allowed disabled:bg-theme-accent/40 disabled:opacity-80 disabled:dark:text-zinc-50", + "disabled:bg-theme-accent/40 disabled:opacity-80 disabled:dark:text-zinc-50", "dark:text-neutral-800", ), + plain: cn( + "bg-background font-semibold transition-colors duration-200", + "border border-border hover:bg-zinc-50/20 dark:bg-neutral-900/30", + ), }, }, + + defaultVariants: { + variant: "primary", + }, }, ) diff --git a/src/renderer/src/components/ui/command.tsx b/src/renderer/src/components/ui/command.tsx deleted file mode 100644 index b08fbbd036..0000000000 --- a/src/renderer/src/components/ui/command.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import type { DialogProps } from "@radix-ui/react-dialog" -import { Dialog, DialogContent } from "@renderer/components/ui/dialog" -import { cn } from "@renderer/lib/utils" -import { Command as CommandPrimitive } from "cmdk" -import { Search } from "lucide-react" -import * as React from "react" - -const Command = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -Command.displayName = CommandPrimitive.displayName - -type CommandDialogProps = DialogProps - -const CommandDialog = ({ children, ...props }: CommandDialogProps) => ( - - - - {children} - - - -) - -const CommandInput = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -
- - -
-)) - -CommandInput.displayName = CommandPrimitive.Input.displayName - -const CommandList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) - -CommandList.displayName = CommandPrimitive.List.displayName - -const CommandEmpty = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->((props, ref) => ( - -)) - -CommandEmpty.displayName = CommandPrimitive.Empty.displayName - -const CommandGroup = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) - -CommandGroup.displayName = CommandPrimitive.Group.displayName - -const CommandSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -CommandSeparator.displayName = CommandPrimitive.Separator.displayName - -const CommandItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) - -CommandItem.displayName = CommandPrimitive.Item.displayName - -const CommandShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => ( - -) -CommandShortcut.displayName = "CommandShortcut" - -export { - Command, - CommandDialog, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, - CommandShortcut, -} diff --git a/src/renderer/src/components/ui/dialog/dialog.tsx b/src/renderer/src/components/ui/dialog/dialog.tsx deleted file mode 100644 index 024a10da61..0000000000 --- a/src/renderer/src/components/ui/dialog/dialog.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import * as DialogPrimitive from "@radix-ui/react-dialog" -import { cn } from "@renderer/lib/utils" -import { m } from "framer-motion" -import * as React from "react" - -const Dialog = DialogPrimitive.Root - -const DialogTrigger = DialogPrimitive.Trigger - -const DialogPortal = DialogPrimitive.Portal - -const DialogClose = DialogPrimitive.Close - -export const DialogOverlay = ({ - onClick, - zIndex, -}: { - onClick?: () => void - zIndex?: number -}) => ( - - - -) - -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName - -const DialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - {children} - - - Close - - - -)) -DialogContent.displayName = DialogPrimitive.Content.displayName - -const DialogHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -DialogHeader.displayName = "DialogHeader" - -const DialogFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -DialogFooter.displayName = "DialogFooter" - -const DialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogTitle.displayName = DialogPrimitive.Title.displayName - -const DialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogDescription.displayName = DialogPrimitive.Description.displayName - -export { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogPortal, - DialogTitle, - DialogTrigger, -} diff --git a/src/renderer/src/components/ui/dialog/index.ts b/src/renderer/src/components/ui/dialog/index.ts deleted file mode 100644 index 6c5229936c..0000000000 --- a/src/renderer/src/components/ui/dialog/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./dialog" diff --git a/src/renderer/src/components/ui/modal/stacked/modal.tsx b/src/renderer/src/components/ui/modal/stacked/modal.tsx index e368af5290..98286df80b 100644 --- a/src/renderer/src/components/ui/modal/stacked/modal.tsx +++ b/src/renderer/src/components/ui/modal/stacked/modal.tsx @@ -15,7 +15,6 @@ import { } from "react" import { useEventCallback } from "usehooks-ts" -import { DialogOverlay } from "../../dialog" import { Divider } from "../../divider" import { modalMontionConfig } from "./constants" import type { @@ -25,6 +24,25 @@ import type { import { CurrentModalContext, modalStackAtom } from "./context" import type { ModalProps } from "./types" +const DialogOverlay = ({ + onClick, + zIndex, +}: { + onClick?: () => void + zIndex?: number +}) => ( + + + +) + export const ModalInternal: Component<{ item: ModalProps & { id: string } index: number @@ -185,10 +203,10 @@ export const ModalInternal: Component<{ onClick={stopPropagation} >
- + {title} - +
diff --git a/src/renderer/src/store/subscription.ts b/src/renderer/src/store/subscription.ts index 71aaaba5b5..7b196fee32 100644 --- a/src/renderer/src/store/subscription.ts +++ b/src/renderer/src/store/subscription.ts @@ -19,6 +19,7 @@ interface SubscriptionActions { markReadByView: (view?: FeedViewType) => void internal_reset: () => void clear: () => void + deleteCategory: (ids: string[]) => void } const emptyDataIdByView: Record = { @@ -65,13 +66,15 @@ export const useSubscriptionStore = createZustandStore< })) } - set((state) => produce(state, (state) => { - res.data.forEach((subscription) => { - state.data[subscription.feeds.id] = subscription - state.dataIdByView[subscription.view].push(subscription.feeds.id) - return state - }) - })) + set((state) => + produce(state, (state) => { + res.data.forEach((subscription) => { + state.data[subscription.feeds.id] = subscription + state.dataIdByView[subscription.view].push(subscription.feeds.id) + return state + }) + }), + ) return res.data }, @@ -92,6 +95,19 @@ export const useSubscriptionStore = createZustandStore< } } }, + deleteCategory(ids) { + const idSet = new Set(ids) + console.log("deleteCategory", ids) + set((state) => + produce(state, (state) => { + Object.keys(state.data).forEach((id) => { + if (idSet.has(id)) { + state.data[id].category = null + } + }) + }), + ) + }, })) export const subscriptionActions = getStoreActions(useSubscriptionStore) From 1ab86fbb925e490ae9bd36b1262bdc02bb060da6 Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 14 Jun 2024 17:37:48 +0800 Subject: [PATCH 2/6] fix: command Signed-off-by: Innei --- src/renderer/src/components/ui/command.tsx | 133 +++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/renderer/src/components/ui/command.tsx diff --git a/src/renderer/src/components/ui/command.tsx b/src/renderer/src/components/ui/command.tsx new file mode 100644 index 0000000000..cf54ae0b2d --- /dev/null +++ b/src/renderer/src/components/ui/command.tsx @@ -0,0 +1,133 @@ +import { cn } from "@renderer/lib/utils" +import { Command as CommandPrimitive } from "cmdk" +import { Search } from "lucide-react" +import * as React from "react" + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = CommandPrimitive.displayName + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => ( + +) +CommandShortcut.displayName = "CommandShortcut" + +export { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} From 4093810378c447bbfbfccae79e2c00cea227a709 Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 14 Jun 2024 17:41:04 +0800 Subject: [PATCH 3/6] fix: modal controller Signed-off-by: Innei --- src/renderer/src/components/ui/modal/stacked/modal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/components/ui/modal/stacked/modal.tsx b/src/renderer/src/components/ui/modal/stacked/modal.tsx index 98286df80b..92137dce7a 100644 --- a/src/renderer/src/components/ui/modal/stacked/modal.tsx +++ b/src/renderer/src/components/ui/modal/stacked/modal.tsx @@ -189,6 +189,7 @@ export const ModalInternal: Component<{ Date: Fri, 14 Jun 2024 17:45:33 +0800 Subject: [PATCH 4/6] fix: ci Signed-off-by: Innei --- .github/workflows/lint.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 05fa736bff..e0b0ebfda6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,5 +34,6 @@ jobs: - name: Lint and Typecheck run: | - pnpm run typecheck && npm run lint + pnpm run typecheck + npm run lint pnpm run test From 92cdd05d6ca0d94b01496bdbc75deaf678f2ccda Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 14 Jun 2024 17:50:21 +0800 Subject: [PATCH 5/6] fix: type error Signed-off-by: Innei --- .../src/components/entry-column/index.tsx | 52 +++++++++---------- .../components/entry-column/video-item.tsx | 2 +- src/renderer/src/global.d.ts | 1 + src/renderer/src/hooks/biz/useBizQuery.ts | 7 ++- .../src/hooks/biz/useEntryActions.tsx | 10 ++-- .../(with-layout)/feed/[:id]/index.tsx | 6 +-- 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/renderer/src/components/entry-column/index.tsx b/src/renderer/src/components/entry-column/index.tsx index 82d178b88b..471c8ca0a0 100644 --- a/src/renderer/src/components/entry-column/index.tsx +++ b/src/renderer/src/components/entry-column/index.tsx @@ -20,6 +20,7 @@ import { } from "@renderer/store" import { entryActions } from "@renderer/store/entry/entry" import { useEntryIdsByFeedIdOrView } from "@renderer/store/entry/hooks" +import type { HTMLMotionProps } from "framer-motion" import { m } from "framer-motion" import hotkeys from "hotkeys-js" import { useAtom, useAtomValue } from "jotai" @@ -109,7 +110,7 @@ export function EntryColumn() { endReached: () => entries.hasNextPage && entries.fetchNextPage(), data: entriesIds, itemContent: useCallback( - (index: number, entryId: string) => { + (_, entryId: string) => { if (!entryId) return null return ( @@ -300,32 +301,31 @@ const ListContent = forwardRef((props, ref) => (
)) -const EmptyList = forwardRef< - HTMLDivElement, - React.DetailedHTMLProps, HTMLDivElement> ->((props, ref) => { - const unreadOnly = useAtomValue(unreadOnlyAtom) +const EmptyList = forwardRef>( + (props, ref) => { + const unreadOnly = useAtomValue(unreadOnlyAtom) - return ( - - {unreadOnly ? ( - <> - - Zero Unread - - ) : ( -
- - Zero Items -
- )} -
- ) -}) + return ( + + {unreadOnly ? ( + <> + + Zero Unread + + ) : ( +
+ + Zero Items +
+ )} +
+ ) + }, +) const EntryList: FC> = ({ ...virtuosoOptions diff --git a/src/renderer/src/components/entry-column/video-item.tsx b/src/renderer/src/components/entry-column/video-item.tsx index bb7514a1f2..5460754435 100644 --- a/src/renderer/src/components/entry-column/video-item.tsx +++ b/src/renderer/src/components/entry-column/video-item.tsx @@ -13,7 +13,7 @@ import type { UniversalItemProps } from "./types" export function VideoItem({ entryId, entryPreview, translation }: UniversalItemProps) { const entry = useEntry(entryId) || entryPreview - const iframeSrc = useMemo(() => urlToIframe(entry.entries.url), [entry.entries.url]) + const iframeSrc = useMemo(() => urlToIframe(entry?.entries.url), [entry?.entries.url]) const ref = useRef(null) const [hovered, setHovered] = useState(false) diff --git a/src/renderer/src/global.d.ts b/src/renderer/src/global.d.ts index d989c383fa..3df20ae838 100644 --- a/src/renderer/src/global.d.ts +++ b/src/renderer/src/global.d.ts @@ -8,6 +8,7 @@ declare global { className?: string } & PropsWithChildren & P + export type Nullable = T | null | undefined } diff --git a/src/renderer/src/hooks/biz/useBizQuery.ts b/src/renderer/src/hooks/biz/useBizQuery.ts index 60d440528e..57e3537bd1 100644 --- a/src/renderer/src/hooks/biz/useBizQuery.ts +++ b/src/renderer/src/hooks/biz/useBizQuery.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { RequestError } from "@renderer/biz/error" import type { DefinedQuery } from "@renderer/lib/defineQuery" import type { InfiniteData, @@ -20,7 +19,7 @@ export type SafeReturnType = T extends (...args: any[]) => infer R export type CombinedObject = T & U export function useBizQuery< TQuery extends DefinedQuery, - TError = FetchError | RequestError, + TError = FetchError, TQueryFnData = Awaited>, TData = TQueryFnData, >( @@ -50,14 +49,14 @@ export function useBizQuery< export function useBizInfiniteQuery< T extends DefinedQuery, - E = FetchError | RequestError, + E = FetchError, FNR = Awaited>, R = FNR, >( query: T, options: Omit, "queryKey" | "queryFn">, ): CombinedObject< - UseInfiniteQueryResult, FetchError | RequestError>, + UseInfiniteQueryResult, FetchError>, { key: T["key"], fn: T["fn"] } > { // @ts-expect-error diff --git a/src/renderer/src/hooks/biz/useEntryActions.tsx b/src/renderer/src/hooks/biz/useEntryActions.tsx index 36643ac769..e588c2d688 100644 --- a/src/renderer/src/hooks/biz/useEntryActions.tsx +++ b/src/renderer/src/hooks/biz/useEntryActions.tsx @@ -8,7 +8,7 @@ import { ofetch } from "ofetch" import { useMemo } from "react" import { toast } from "sonner" -export const useCollect = (entry: EntryModel | undefined) => +export const useCollect = (entry: Nullable) => useMutation({ mutationFn: async () => entry && @@ -33,7 +33,7 @@ export const useCollect = (entry: EntryModel | undefined) => }, }) -export const useUnCollect = (entry: EntryModel | undefined) => +export const useUnCollect = (entry: Nullable) => useMutation({ mutationFn: async () => entry && @@ -56,7 +56,7 @@ export const useUnCollect = (entry: EntryModel | undefined) => }, }) -export const useRead = (entry: EntryModel | undefined) => +export const useRead = (entry: Nullable) => useMutation({ mutationFn: async () => entry && @@ -72,7 +72,7 @@ export const useRead = (entry: EntryModel | undefined) => entryActions.markRead(entry.feeds.id, entry.entries.id, true) }, }) -export const useUnread = (entry: EntryModel | undefined) => +export const useUnread = (entry: Nullable) => useMutation({ mutationFn: async () => entry && @@ -94,7 +94,7 @@ export const useEntryActions = ({ entry, }: { view?: number - entry?: EntryModel + entry?: EntryModel | null }) => { const checkEagle = useQuery({ queryKey: ["check-eagle"], diff --git a/src/renderer/src/pages/(external)/(with-layout)/feed/[:id]/index.tsx b/src/renderer/src/pages/(external)/(with-layout)/feed/[:id]/index.tsx index add1ac5d77..a49b9a2a1b 100644 --- a/src/renderer/src/pages/(external)/(with-layout)/feed/[:id]/index.tsx +++ b/src/renderer/src/pages/(external)/(with-layout)/feed/[:id]/index.tsx @@ -11,6 +11,7 @@ import { ListItemHoverOverlay } from "@renderer/components/ui/list-item-hover-ov import { APP_NAME, views } from "@renderer/lib/constants" import { FeedViewType } from "@renderer/lib/enum" import { cn } from "@renderer/lib/utils" +import type { FeedModel } from "@renderer/models" import { useEntriesPreview } from "@renderer/queries/entries" import { useFeed } from "@renderer/queries/feed" import { DEEPLINK_SCHEME } from "@shared/constants" @@ -67,7 +68,6 @@ export function Component() { {feed.data.feed.title} {" "} | - {" "} {APP_NAME} @@ -91,7 +91,6 @@ export function Component() { {feed.data.readCount} {" "} reads on - {" "} {APP_NAME}
@@ -122,7 +121,8 @@ export function Component() { entryId="" entryPreview={{ entries: entry, - feeds: feed.data.feed, + // @ts-expect-error + feeds: feed.data.feed as FeedModel, read: false, }} /> From 7419d7a63bedb6b708104e83347a5cad7ef8e1c4 Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 14 Jun 2024 17:51:48 +0800 Subject: [PATCH 6/6] chore: cleanup Signed-off-by: Innei --- src/renderer/src/store/subscription.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/store/subscription.ts b/src/renderer/src/store/subscription.ts index 7b196fee32..4e1a47fe6e 100644 --- a/src/renderer/src/store/subscription.ts +++ b/src/renderer/src/store/subscription.ts @@ -97,7 +97,7 @@ export const useSubscriptionStore = createZustandStore< }, deleteCategory(ids) { const idSet = new Set(ids) - console.log("deleteCategory", ids) + set((state) => produce(state, (state) => { Object.keys(state.data).forEach((id) => {