feat: optimize daily report and report modal for social media
Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
a625d04328
commit
252962c188
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#fff" fill-opacity=".01" d="M24 0v24H0V0z"/><path fill="#10161F" d="M12 16a1 1 0 1 0 0 2zm.002 2a1 1 0 0 0 0-2zm-3.627-7.875a1 1 0 1 0 2 0zM11 14a1 1 0 1 0 2 0zm1.976-1.437.372.928zM20 12a8 8 0 0 1-8 8v2c5.523 0 10-4.477 10-10zm-8 8a8 8 0 0 1-8-8H2c0 5.523 4.477 10 10 10zm-8-8a8 8 0 0 1 8-8V2C6.477 2 2 6.477 2 12zm8-8a8 8 0 0 1 8 8h2c0-5.523-4.477-10-10-10zm0 14h.002v-2H12zm1.625-7.875c0 .682-.42 1.269-1.02 1.51l.743 1.856a3.626 3.626 0 0 0 2.277-3.366zm-3.25 0c0-.897.727-1.625 1.625-1.625v-2a3.625 3.625 0 0 0-3.625 3.625zM12 8.5c.898 0 1.625.728 1.625 1.625h2A3.625 3.625 0 0 0 12 6.5zm-1 5.25V14h2v-.25zm1.604-2.116c-.675.27-1.604.963-1.604 2.116h2c0 .02-.012.003.043-.059a.837.837 0 0 1 .305-.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 800 B |
|
|
@ -2,6 +2,9 @@ import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
|||
import { cn } from "@renderer/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
/**
|
||||
* @deprecated please use `src/renderer/src/components/ui/collapse/Collapse.tsx`
|
||||
*/
|
||||
const Accordion = AccordionPrimitive.Root
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
|
|
|
|||
|
|
@ -1,15 +1,32 @@
|
|||
import { cn } from "@renderer/lib/utils"
|
||||
import { createContextState } from "foxact/context-state"
|
||||
import type { Variants } from "framer-motion"
|
||||
import { AnimatePresence, m } from "framer-motion"
|
||||
import * as React from "react"
|
||||
|
||||
import { microReboundPreset } from "../constants/spring"
|
||||
|
||||
interface CollapseProps {
|
||||
title: React.ReactNode
|
||||
hideArrow?: boolean
|
||||
}
|
||||
const [CollapseStateProvider, useCurrentCollapseId, useSetCurrentCollapseId] =
|
||||
createContextState<string | null>(null)
|
||||
export const CollapseGroup: Component = ({ children }) => (
|
||||
<CollapseStateProvider>{children}</CollapseStateProvider>
|
||||
)
|
||||
export const Collapse: Component<CollapseProps> = (props) => {
|
||||
const [isOpened, setIsOpened] = React.useState(false)
|
||||
const id = React.useId()
|
||||
const setCurrentId = useSetCurrentCollapseId()
|
||||
const currentId = useCurrentCollapseId()
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isOpened) {
|
||||
setCurrentId(id)
|
||||
}
|
||||
}, [id, isOpened, setCurrentId])
|
||||
React.useEffect(() => {
|
||||
setIsOpened(currentId === id)
|
||||
}, [currentId, id])
|
||||
return (
|
||||
<CollapseControlled
|
||||
isOpened={isOpened}
|
||||
|
|
@ -34,14 +51,16 @@ export const CollapseControlled: Component<
|
|||
onClick={() => props.onOpenChange(!props.isOpened)}
|
||||
>
|
||||
<span className="w-0 shrink grow truncate">{props.title}</span>
|
||||
<div className="shrink-0 text-gray-400">
|
||||
<i
|
||||
className={cn(
|
||||
"i-mingcute-down-line duration-200",
|
||||
props.isOpened ? "rotate-180" : "",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{!props.hideArrow && (
|
||||
<div className="shrink-0 text-gray-400">
|
||||
<i
|
||||
className={cn(
|
||||
"i-mingcute-down-line duration-200",
|
||||
props.isOpened ? "rotate-180" : "",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CollapseContent isOpened={props.isOpened}>
|
||||
{props.children}
|
||||
|
|
@ -57,7 +76,11 @@ export const CollapseContent: Component<{
|
|||
open: {
|
||||
opacity: 1,
|
||||
height: "auto",
|
||||
transition: microReboundPreset,
|
||||
|
||||
transition: {
|
||||
type: "spring",
|
||||
mass: 0.2,
|
||||
},
|
||||
},
|
||||
collapsed: {
|
||||
opacity: 0,
|
||||
|
|
|
|||
|
|
@ -18,4 +18,4 @@ export const modalMontionConfig = {
|
|||
transition: microReboundPreset,
|
||||
}
|
||||
|
||||
export const MODAL_STACK_Z_INDEX = 100
|
||||
export const MODAL_STACK_Z_INDEX = 40
|
||||
|
|
|
|||
|
|
@ -0,0 +1,192 @@
|
|||
import {
|
||||
setGeneralSetting,
|
||||
useGeneralSettingKey,
|
||||
} from "@renderer/atoms/settings/general"
|
||||
import { useWhoami } from "@renderer/atoms/user"
|
||||
import { ActionButton } from "@renderer/components/ui/button"
|
||||
import { DividerVertical } from "@renderer/components/ui/divider"
|
||||
import { useModalStack } from "@renderer/components/ui/modal"
|
||||
import { EllipsisHorizontalTextWithTooltip } from "@renderer/components/ui/typography"
|
||||
import {
|
||||
FEED_COLLECTION_LIST,
|
||||
ROUTE_ENTRY_PENDING,
|
||||
views,
|
||||
} from "@renderer/constants"
|
||||
import { shortcuts } from "@renderer/constants/shortcuts"
|
||||
import {
|
||||
useRouteParms,
|
||||
} from "@renderer/hooks/biz/useRouteParams"
|
||||
import { useIsOnline } from "@renderer/hooks/common"
|
||||
import { FeedViewType } from "@renderer/lib/enum"
|
||||
import { cn, getOS, isBizId } from "@renderer/lib/utils"
|
||||
import { useRefreshFeedMutation } from "@renderer/queries/feed"
|
||||
import { useFeedById, useFeedHeaderTitle } from "@renderer/store/feed"
|
||||
import type { FC } from "react"
|
||||
|
||||
import { Daily } from "../entry-content/daily"
|
||||
import { EntryHeader } from "../entry-content/header"
|
||||
import { MarkAllButton } from "./mark-all-button"
|
||||
|
||||
export const EntryListHeader: FC<{
|
||||
totalCount: number
|
||||
refetch: () => void
|
||||
isRefreshing: boolean
|
||||
hasUpdate: boolean
|
||||
}> = ({ totalCount, refetch, isRefreshing, hasUpdate }) => {
|
||||
const routerParams = useRouteParms()
|
||||
|
||||
const unreadOnly = useGeneralSettingKey("unreadOnly")
|
||||
|
||||
const { feedId, entryId, view } = routerParams
|
||||
|
||||
const headerTitle = useFeedHeaderTitle()
|
||||
const os = getOS()
|
||||
|
||||
const titleAtBottom = window.electron && os === "macOS"
|
||||
const isInCollectionList = feedId === FEED_COLLECTION_LIST
|
||||
|
||||
const titleInfo = !!headerTitle && (
|
||||
<div className={!titleAtBottom ? "min-w-0 translate-y-1" : void 0}>
|
||||
<div className="min-w-0 break-all text-lg font-bold leading-none">
|
||||
<EllipsisHorizontalTextWithTooltip className="inline-block !w-auto max-w-full">
|
||||
{headerTitle}
|
||||
</EllipsisHorizontalTextWithTooltip>
|
||||
</div>
|
||||
<div className="text-xs font-medium text-zinc-400">
|
||||
{totalCount || 0}
|
||||
{" "}
|
||||
{unreadOnly && !isInCollectionList ? "Unread" : ""}
|
||||
{" "}
|
||||
Items
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
const { mutateAsync: refreshFeed, isPending } = useRefreshFeedMutation(
|
||||
routerParams.feedId,
|
||||
)
|
||||
|
||||
const user = useWhoami()
|
||||
const isOnline = useIsOnline()
|
||||
|
||||
const feed = useFeedById(routerParams.feedId)
|
||||
|
||||
const titleStyleBasedView = [
|
||||
"pl-12",
|
||||
"pl-7",
|
||||
"pl-7",
|
||||
"pl-7",
|
||||
"px-5",
|
||||
"pl-12",
|
||||
]
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"mb-2 flex w-full flex-col pr-4 pt-2.5",
|
||||
titleStyleBasedView[view],
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full",
|
||||
titleAtBottom ? "justify-end" : "justify-between",
|
||||
)}
|
||||
>
|
||||
{!titleAtBottom && titleInfo}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"relative z-[1] flex items-center gap-1 self-baseline text-zinc-500",
|
||||
(isInCollectionList || !headerTitle) &&
|
||||
"pointer-events-none opacity-0",
|
||||
|
||||
"translate-x-[6px]",
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{views[view].wideMode &&
|
||||
entryId &&
|
||||
entryId !== ROUTE_ENTRY_PENDING && (
|
||||
<>
|
||||
<EntryHeader view={view} entryId={entryId} />
|
||||
<DividerVertical className="w-px" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{view === FeedViewType.SocialMedia && <DailyReportButton />}
|
||||
|
||||
{isOnline ? (
|
||||
feed?.ownerUserId === user?.id && isBizId(routerParams.feedId!) ?
|
||||
(
|
||||
<ActionButton
|
||||
tooltip="Refresh"
|
||||
onClick={() => {
|
||||
refreshFeed()
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={cn(
|
||||
"i-mgc-refresh-2-cute-re",
|
||||
isPending && "animate-spin",
|
||||
)}
|
||||
/>
|
||||
</ActionButton>
|
||||
) :
|
||||
(
|
||||
<ActionButton
|
||||
tooltip={hasUpdate ? "New entries available" : "Refetch"}
|
||||
onClick={() => {
|
||||
refetch()
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={cn(
|
||||
"i-mgc-refresh-2-cute-re",
|
||||
isRefreshing && "animate-spin",
|
||||
hasUpdate && "text-theme-accent",
|
||||
)}
|
||||
/>
|
||||
</ActionButton>
|
||||
)
|
||||
) : null}
|
||||
<ActionButton
|
||||
tooltip={unreadOnly ? "Unread Only" : "All"}
|
||||
shortcut={shortcuts.entries.toggleUnreadOnly.key}
|
||||
onClick={() => setGeneralSetting("unreadOnly", !unreadOnly)}
|
||||
>
|
||||
{unreadOnly ? (
|
||||
<i className="i-mgc-round-cute-fi" />
|
||||
) : (
|
||||
<i className="i-mgc-round-cute-re" />
|
||||
)}
|
||||
</ActionButton>
|
||||
<MarkAllButton />
|
||||
</div>
|
||||
</div>
|
||||
{titleAtBottom && titleInfo}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DailyReportButton: FC = () => {
|
||||
const { present } = useModalStack()
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
onClick={() => {
|
||||
present({
|
||||
title: "Daily Report",
|
||||
content: () => (
|
||||
<Daily
|
||||
view={FeedViewType.SocialMedia}
|
||||
|
||||
/>
|
||||
),
|
||||
})
|
||||
}}
|
||||
tooltip="Daily Report"
|
||||
>
|
||||
<i className="i-mgc-magic-2-cute-re" />
|
||||
</ActionButton>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,36 +1,22 @@
|
|||
import {
|
||||
setGeneralSetting,
|
||||
useGeneralSettingKey,
|
||||
} from "@renderer/atoms/settings/general"
|
||||
import { useWhoami } from "@renderer/atoms/user"
|
||||
import { m } from "@renderer/components/common/Motion"
|
||||
import { FeedFoundCanBeFollowError } from "@renderer/components/errors/FeedFoundCanBeFollowErrorFallback"
|
||||
import { FeedNotFound } from "@renderer/components/errors/FeedNotFound"
|
||||
import { AutoResizeHeight } from "@renderer/components/ui/auto-resize-height"
|
||||
import { ActionButton } from "@renderer/components/ui/button"
|
||||
import { DividerVertical } from "@renderer/components/ui/divider"
|
||||
import { LoadingCircle } from "@renderer/components/ui/loading"
|
||||
import { ScrollArea } from "@renderer/components/ui/scroll-area"
|
||||
import { EllipsisHorizontalTextWithTooltip } from "@renderer/components/ui/typography"
|
||||
import {
|
||||
FEED_COLLECTION_LIST,
|
||||
ROUTE_ENTRY_PENDING,
|
||||
ROUTE_FEED_PENDING,
|
||||
views,
|
||||
} from "@renderer/constants"
|
||||
import { shortcuts } from "@renderer/constants/shortcuts"
|
||||
import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry"
|
||||
import {
|
||||
useRouteParamsSelector,
|
||||
useRouteParms,
|
||||
} from "@renderer/hooks/biz/useRouteParams"
|
||||
import { useIsOnline } from "@renderer/hooks/common/useIsOnline"
|
||||
import { cn, getOS, isBizId } from "@renderer/lib/utils"
|
||||
import { EntryHeader } from "@renderer/modules/entry-content/header"
|
||||
import { useFeed, useRefreshFeedMutation } from "@renderer/queries/feed"
|
||||
import { cn, isBizId } from "@renderer/lib/utils"
|
||||
import { useFeed } from "@renderer/queries/feed"
|
||||
import { entryActions, useEntry } from "@renderer/store/entry"
|
||||
import { useFeedById, useFeedHeaderTitle } from "@renderer/store/feed"
|
||||
import type { FC } from "react"
|
||||
import { useCallback, useEffect, useRef } from "react"
|
||||
import type {
|
||||
ScrollSeekConfiguration,
|
||||
|
|
@ -39,6 +25,7 @@ import type {
|
|||
} from "react-virtuoso"
|
||||
import { VirtuosoGrid } from "react-virtuoso"
|
||||
|
||||
import { EntryListHeader } from "./EntryListHeader"
|
||||
import { useEntriesByView, useEntryMarkReadHandler } from "./hooks"
|
||||
import {
|
||||
EntryItem,
|
||||
|
|
@ -46,7 +33,6 @@ import {
|
|||
EntryItemSkeletonWithDelayShow,
|
||||
} from "./item"
|
||||
import { EntryEmptyList, EntryList, EntryListContent } from "./lists"
|
||||
import { MarkAllButton } from "./mark-all-button"
|
||||
import { girdClassNames } from "./styles"
|
||||
|
||||
const scrollSeekConfiguration: ScrollSeekConfiguration = {
|
||||
|
|
@ -147,7 +133,7 @@ export function EntryColumn() {
|
|||
!entries.isLoading &&
|
||||
!entries.error && <AddFeedHelper />}
|
||||
|
||||
<ListHeader
|
||||
<EntryListHeader
|
||||
refetch={entries.refetch}
|
||||
isRefreshing={isRefreshing}
|
||||
totalCount={virtuosoOptions.totalCount}
|
||||
|
|
@ -228,141 +214,3 @@ const AddFeedHelper = () => {
|
|||
|
||||
throw new FeedFoundCanBeFollowError(feedQuery.data.feed)
|
||||
}
|
||||
|
||||
const ListHeader: FC<{
|
||||
totalCount: number
|
||||
refetch: () => void
|
||||
isRefreshing: boolean
|
||||
hasUpdate: boolean
|
||||
}> = ({ totalCount, refetch, isRefreshing, hasUpdate }) => {
|
||||
const routerParams = useRouteParms()
|
||||
|
||||
const unreadOnly = useGeneralSettingKey("unreadOnly")
|
||||
|
||||
const { feedId, entryId, view } = routerParams
|
||||
|
||||
const headerTitle = useFeedHeaderTitle()
|
||||
const os = getOS()
|
||||
|
||||
const titleAtBottom = window.electron && os === "macOS"
|
||||
const isInCollectionList = feedId === FEED_COLLECTION_LIST
|
||||
|
||||
const titleInfo = !!headerTitle && (
|
||||
<div className={!titleAtBottom ? "min-w-0 translate-y-1" : void 0}>
|
||||
<div className="min-w-0 break-all text-lg font-bold leading-none">
|
||||
<EllipsisHorizontalTextWithTooltip className="inline-block !w-auto max-w-full">
|
||||
{headerTitle}
|
||||
</EllipsisHorizontalTextWithTooltip>
|
||||
</div>
|
||||
<div className="text-xs font-medium text-zinc-400">
|
||||
{totalCount || 0}
|
||||
{" "}
|
||||
{unreadOnly && !isInCollectionList ? "Unread" : ""}
|
||||
{" "}
|
||||
Items
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
const { mutateAsync: refreshFeed, isPending } = useRefreshFeedMutation(
|
||||
routerParams.feedId,
|
||||
)
|
||||
|
||||
const user = useWhoami()
|
||||
const isOnline = useIsOnline()
|
||||
|
||||
const feed = useFeedById(routerParams.feedId)
|
||||
|
||||
const titleStyleBasedView = [
|
||||
"pl-12",
|
||||
"pl-7",
|
||||
"pl-7",
|
||||
"pl-7",
|
||||
"px-5",
|
||||
"pl-12",
|
||||
]
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"mb-2 flex w-full flex-col pr-4 pt-2.5",
|
||||
titleStyleBasedView[view],
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full",
|
||||
titleAtBottom ? "justify-end" : "justify-between",
|
||||
)}
|
||||
>
|
||||
{!titleAtBottom && titleInfo}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"relative z-[1] flex items-center gap-1 self-baseline text-zinc-500",
|
||||
(isInCollectionList || !headerTitle) &&
|
||||
"pointer-events-none opacity-0",
|
||||
|
||||
"translate-x-[6px]",
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{views[view].wideMode &&
|
||||
entryId &&
|
||||
entryId !== ROUTE_ENTRY_PENDING && (
|
||||
<>
|
||||
<EntryHeader view={view} entryId={entryId} />
|
||||
<DividerVertical className="w-px" />
|
||||
</>
|
||||
)}
|
||||
{isOnline ? (
|
||||
feed?.ownerUserId === user?.id && isBizId(routerParams.feedId!) ?
|
||||
(
|
||||
<ActionButton
|
||||
tooltip="Refresh"
|
||||
onClick={() => {
|
||||
refreshFeed()
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={cn(
|
||||
"i-mgc-refresh-2-cute-re",
|
||||
isPending && "animate-spin",
|
||||
)}
|
||||
/>
|
||||
</ActionButton>
|
||||
) :
|
||||
(
|
||||
<ActionButton
|
||||
tooltip={hasUpdate ? "New entries available" : "Refetch"}
|
||||
onClick={() => {
|
||||
refetch()
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={cn(
|
||||
"i-mgc-refresh-2-cute-re",
|
||||
isRefreshing && "animate-spin",
|
||||
hasUpdate && "text-theme-accent",
|
||||
)}
|
||||
/>
|
||||
</ActionButton>
|
||||
)
|
||||
) : null}
|
||||
<ActionButton
|
||||
tooltip={unreadOnly ? "Unread Only" : "All"}
|
||||
shortcut={shortcuts.entries.toggleUnreadOnly.key}
|
||||
onClick={() => setGeneralSetting("unreadOnly", !unreadOnly)}
|
||||
>
|
||||
{unreadOnly ? (
|
||||
<i className="i-mgc-round-cute-fi" />
|
||||
) : (
|
||||
<i className="i-mgc-round-cute-re" />
|
||||
)}
|
||||
</ActionButton>
|
||||
<MarkAllButton />
|
||||
</div>
|
||||
</div>
|
||||
{titleAtBottom && titleInfo}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { PopoverPortal } from "@radix-ui/react-popover"
|
||||
import { ActionButton, StyledButton } from "@renderer/components/ui/button"
|
||||
import {
|
||||
Popover,
|
||||
|
|
@ -7,19 +8,22 @@ import {
|
|||
} from "@renderer/components/ui/popover"
|
||||
import { shortcuts } from "@renderer/constants/shortcuts"
|
||||
import { useRouteParms } from "@renderer/hooks/biz/useRouteParams"
|
||||
import { subscriptionActions, useFolderFeedsByFeedId } from "@renderer/store/subscription"
|
||||
import { useCallback, useState } from "react"
|
||||
import {
|
||||
subscriptionActions,
|
||||
useFolderFeedsByFeedId,
|
||||
} from "@renderer/store/subscription"
|
||||
import { forwardRef, useCallback, useState } from "react"
|
||||
|
||||
export const MarkAllButton = ({
|
||||
filter,
|
||||
className,
|
||||
}: {
|
||||
filter?: {
|
||||
startTime: number
|
||||
endTime: number
|
||||
export const MarkAllButton = forwardRef<
|
||||
HTMLButtonElement,
|
||||
{
|
||||
filter?: {
|
||||
startTime: number
|
||||
endTime: number
|
||||
}
|
||||
className?: string
|
||||
}
|
||||
className?: string
|
||||
}) => {
|
||||
>(({ filter, className }, ref) => {
|
||||
const [markPopoverOpen, setMarkPopoverOpen] = useState(false)
|
||||
|
||||
const routerParams = useRouteParms()
|
||||
|
|
@ -35,13 +39,13 @@ export const MarkAllButton = ({
|
|||
if (typeof routerParams.feedId === "number" || routerParams.isAllFeeds) {
|
||||
subscriptionActions.markReadByView(view, filter)
|
||||
} else if (folderIds) {
|
||||
subscriptionActions.markReadByFeedIds(view, folderIds, filter)
|
||||
} else if (routerParams.feedId) {
|
||||
subscriptionActions.markReadByFeedIds(
|
||||
view,
|
||||
folderIds,
|
||||
routerParams.feedId?.split(","),
|
||||
filter,
|
||||
)
|
||||
} else if (routerParams.feedId) {
|
||||
subscriptionActions.markReadByFeedIds(view, routerParams.feedId?.split(","), filter)
|
||||
}
|
||||
}, [feedId, folderIds, filter, routerParams])
|
||||
|
||||
|
|
@ -52,26 +56,30 @@ export const MarkAllButton = ({
|
|||
shortcut={shortcuts.entries.markAllAsRead.key}
|
||||
tooltip="Mark All as Read"
|
||||
className={className}
|
||||
ref={ref}
|
||||
>
|
||||
<i className="i-mgc-check-circle-cute-re" />
|
||||
</ActionButton>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="flex w-fit flex-col items-center justify-center gap-3 text-[0.94rem] font-medium">
|
||||
<div>Mark all as read?</div>
|
||||
<div className="space-x-4">
|
||||
<PopoverClose>
|
||||
<StyledButton variant="outline">Cancel</StyledButton>
|
||||
</PopoverClose>
|
||||
{/* TODO */}
|
||||
<StyledButton onClick={() => {
|
||||
handleMarkAllAsRead()
|
||||
setMarkPopoverOpen(false)
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</StyledButton>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
<PopoverPortal>
|
||||
<PopoverContent className="flex w-fit flex-col items-center justify-center gap-3 text-[0.94rem] font-medium">
|
||||
<div>Mark all as read?</div>
|
||||
<div className="space-x-4">
|
||||
<PopoverClose>
|
||||
<StyledButton variant="outline">Cancel</StyledButton>
|
||||
</PopoverClose>
|
||||
{/* TODO */}
|
||||
<StyledButton
|
||||
onClick={() => {
|
||||
handleMarkAllAsRead()
|
||||
setMarkPopoverOpen(false)
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</StyledButton>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</PopoverPortal>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { RelativeTime } from "@renderer/components/ui/datetime"
|
|||
import { Media } from "@renderer/components/ui/media"
|
||||
import { usePreviewMedia } from "@renderer/components/ui/media/hooks"
|
||||
import { Skeleton } from "@renderer/components/ui/skeleton"
|
||||
import { views } from "@renderer/constants"
|
||||
import { useAsRead } from "@renderer/hooks/biz/useAsRead"
|
||||
import { useEntryActions } from "@renderer/hooks/biz/useEntryActions"
|
||||
import { useRouteParamsSelector } from "@renderer/hooks/biz/useRouteParams"
|
||||
|
|
@ -177,9 +178,10 @@ export const SocialMediaDateItem = ({
|
|||
const dateString = dateObj.toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" })
|
||||
const startOfDay = new Date(dateObj.setHours(0, 0, 0, 0)).getTime()
|
||||
const endOfDay = new Date(dateObj.setHours(23, 59, 59, 999)).getTime()
|
||||
const view = useRouteParamsSelector((s) => s.view)
|
||||
|
||||
return (
|
||||
<div className={cn(className, "m-auto w-[75ch] gap-3 pl-5 text-base")}>
|
||||
<div className={cn(className, "m-auto w-[75ch] gap-3 pl-5 text-base", views[view].wideMode && "flex justify-center")}>
|
||||
<MarkAllButton
|
||||
filter={{
|
||||
startTime: startOfDay,
|
||||
|
|
|
|||
|
|
@ -1,30 +1,36 @@
|
|||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@renderer/components/ui/accordion"
|
||||
import { Card, CardHeader } from "@renderer/components/ui/card"
|
||||
import { Collapse, CollapseGroup } from "@renderer/components/ui/collapse"
|
||||
import { LoadingCircle } from "@renderer/components/ui/loading"
|
||||
import { Markdown } from "@renderer/components/ui/markdown"
|
||||
import { ScrollArea } from "@renderer/components/ui/scroll-area"
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@renderer/components/ui/tooltip"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@renderer/components/ui/tooltip"
|
||||
import { useAuthQuery } from "@renderer/hooks/common"
|
||||
import { apiClient } from "@renderer/lib/api-fetch"
|
||||
import { defineQuery } from "@renderer/lib/defineQuery"
|
||||
import type { FeedViewType } from "@renderer/lib/enum"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { MarkAllButton } from "@renderer/modules/entry-column/mark-all-button"
|
||||
import type { FC } from "react"
|
||||
import { useState } from "react"
|
||||
|
||||
export const DailyItem = ({
|
||||
value,
|
||||
index,
|
||||
view,
|
||||
}: {
|
||||
value?: string
|
||||
index: 0 | 1
|
||||
view: 0 | 1
|
||||
}) => {
|
||||
type DailyView = Extract<
|
||||
FeedViewType,
|
||||
FeedViewType.Articles | FeedViewType.SocialMedia
|
||||
>
|
||||
enum DayOf {
|
||||
Today,
|
||||
Yesterday,
|
||||
}
|
||||
interface DailyItemProps {
|
||||
view: DailyView
|
||||
day: DayOf
|
||||
}
|
||||
|
||||
const DailyItem = ({ view, day }: DailyItemProps) => {
|
||||
const dateObj = new Date()
|
||||
|
||||
const nowHour = dateObj.getHours()
|
||||
|
|
@ -40,9 +46,10 @@ export const DailyItem = ({
|
|||
dateObj.setDate(dateObj.getDate() - 1)
|
||||
const dayBeforeYesterday8PM = dateObj.setHours(20, 0, 0, 0)
|
||||
|
||||
const isToday = day === DayOf.Today
|
||||
// For index 0, get the last past 8 AM or 8 PM; for index 1, get the second last past 8 AM or 8 PM.
|
||||
if (nowHour >= 20) {
|
||||
if (index === 0) {
|
||||
if (isToday) {
|
||||
endDate = today8PM - 1
|
||||
startDate = today8AM
|
||||
title = "Today"
|
||||
|
|
@ -52,7 +59,7 @@ export const DailyItem = ({
|
|||
title = "Last Night"
|
||||
}
|
||||
} else if (nowHour >= 8) {
|
||||
if (index === 0) {
|
||||
if (isToday) {
|
||||
endDate = today8AM - 1
|
||||
startDate = yesterday8PM
|
||||
title = "Last Night"
|
||||
|
|
@ -62,7 +69,7 @@ export const DailyItem = ({
|
|||
title = "Yesterday"
|
||||
}
|
||||
} else {
|
||||
if (index === 0) {
|
||||
if (isToday) {
|
||||
endDate = yesterday8PM - 1
|
||||
startDate = yesterday8AM
|
||||
title = "Yesterday"
|
||||
|
|
@ -73,6 +80,59 @@ export const DailyItem = ({
|
|||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
hideArrow
|
||||
title={(
|
||||
<div className="flex items-center justify-center gap-2 text-base">
|
||||
<i className="i-mgc-magic-2-cute-re" />
|
||||
<div className="font-medium">
|
||||
{title}
|
||||
's Top News
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<i className="i-mgc-question-cute-re translate-y-px" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<ul className="list-decimal text-wrap pl-4 text-left text-sm">
|
||||
<li>
|
||||
Here is news selected by AI from your timeline
|
||||
{" ("}
|
||||
{new Date(startDate).toLocaleTimeString("en-US", {
|
||||
weekday: "short",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
})}
|
||||
{" "}
|
||||
-
|
||||
{" "}
|
||||
{new Date(endDate + 1).toLocaleTimeString("en-US", {
|
||||
weekday: "short",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
})}
|
||||
{") "}
|
||||
that may be important to you.
|
||||
</li>
|
||||
<li>Update daily at 8 AM and 8 PM.</li>
|
||||
</ul>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
className="mx-auto w-full max-w-lg border-b pb-6 last:border-b-0"
|
||||
>
|
||||
<DailyReportContent endDate={endDate} view={view} startDate={startDate} />
|
||||
</Collapse>
|
||||
)
|
||||
}
|
||||
|
||||
const DailyReportContent: FC<{
|
||||
view: DailyView
|
||||
startDate: number
|
||||
endDate: number
|
||||
}> = ({ endDate, startDate, view }) => {
|
||||
const content = useAuthQuery(
|
||||
defineQuery(["daily", view, startDate, endDate], async () => {
|
||||
const res = await apiClient.ai.daily.$get({
|
||||
|
|
@ -84,72 +144,54 @@ export const DailyItem = ({
|
|||
return res.data
|
||||
}),
|
||||
{
|
||||
enabled: value === `${index}`,
|
||||
meta: {
|
||||
persist: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const [markAllButtonRef, setMarkAllButtonRef] =
|
||||
useState<HTMLButtonElement | null>(null)
|
||||
|
||||
return (
|
||||
<AccordionItem value={`${index}`} className="mx-auto max-w-lg last:border-b-0">
|
||||
<Card className="border-none">
|
||||
<CardHeader className="space-y-0 p-0">
|
||||
<AccordionTrigger className="justify-center py-6 pt-2 hover:no-underline">
|
||||
<div className="flex items-center gap-2 text-base">
|
||||
<i className="i-mgc-magic-2-cute-re" />
|
||||
<div className="font-medium">
|
||||
{title}
|
||||
's Top News
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<i className="i-mgc-information-cute-re" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<ul className="list-decimal pl-4 text-left text-sm">
|
||||
<li>
|
||||
Here is news selected by AI from your timeline
|
||||
{" ("}
|
||||
{new Date(startDate).toLocaleTimeString("en-US", { weekday: "short", hour: "numeric", minute: "numeric" })}
|
||||
{" "}
|
||||
-
|
||||
{" "}
|
||||
{new Date(endDate + 1).toLocaleTimeString("en-US", { weekday: "short", hour: "numeric", minute: "numeric" })}
|
||||
{") "}
|
||||
that may be important to you.
|
||||
</li>
|
||||
<li>Update daily at 8 AM and 8 PM.</li>
|
||||
</ul>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<ScrollArea.ScrollArea
|
||||
mask={false}
|
||||
flex
|
||||
viewportClassName="max-h-[calc(100vh-500px)]"
|
||||
>
|
||||
{content.isLoading ? (
|
||||
<LoadingCircle size="large" className="text-center" />
|
||||
) : (
|
||||
content.data && (
|
||||
<Markdown className="prose-sm px-6 prose-p:my-1 prose-ul:my-1">{content.data}</Markdown>
|
||||
)
|
||||
)}
|
||||
</ScrollArea.ScrollArea>
|
||||
<div className="mt-4 flex items-center pl-3.5">
|
||||
<MarkAllButton
|
||||
filter={{
|
||||
startTime: startDate,
|
||||
endTime: endDate,
|
||||
}}
|
||||
className="text-[1.05rem]"
|
||||
/>
|
||||
<span>Mark all as read</span>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</AccordionItem>
|
||||
<Card className="border-none bg-transparent">
|
||||
<CardHeader className="space-y-0 p-0">
|
||||
<ScrollArea.ScrollArea
|
||||
mask={false}
|
||||
flex
|
||||
viewportClassName="max-h-[calc(100vh-500px)]"
|
||||
>
|
||||
{content.isLoading ? (
|
||||
<LoadingCircle size="large" className="mt-8 text-center" />
|
||||
) : (
|
||||
content.data && (
|
||||
<Markdown className="prose-sm mt-4 px-6 prose-p:my-1 prose-ul:my-1">
|
||||
{content.data}
|
||||
</Markdown>
|
||||
)
|
||||
)}
|
||||
</ScrollArea.ScrollArea>
|
||||
{content.data && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
markAllButtonRef?.click()
|
||||
}}
|
||||
className="!mt-4 ml-auto flex items-center rounded-lg py-1 pr-2 duration-200 hover:!bg-theme-button-hover"
|
||||
>
|
||||
<MarkAllButton
|
||||
ref={setMarkAllButtonRef}
|
||||
filter={{
|
||||
startTime: startDate,
|
||||
endTime: endDate,
|
||||
}}
|
||||
className="pointer-events-none text-[1.05rem]"
|
||||
/>
|
||||
<span>Mark all as read</span>
|
||||
</button>
|
||||
)}
|
||||
</CardHeader>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -157,17 +199,14 @@ export const Daily = ({
|
|||
view,
|
||||
className,
|
||||
}: {
|
||||
view: 0 | 1
|
||||
view: DailyView
|
||||
className?: string
|
||||
}) => {
|
||||
const [value, setValue] = useState<string>()
|
||||
}) => (
|
||||
<div className={cn(className, "mx-auto flex w-[75ch] flex-col gap-6")}>
|
||||
<CollapseGroup>
|
||||
<DailyItem day={DayOf.Today} view={view} />
|
||||
|
||||
return (
|
||||
<div className={cn(className, "mx-auto w-[75ch] gap-3")}>
|
||||
<Accordion type="single" className="space-y-4" collapsible value={value} onValueChange={setValue}>
|
||||
<DailyItem value={value} index={0} view={view} />
|
||||
<DailyItem value={value} index={1} view={view} />
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<DailyItem day={DayOf.Yesterday} view={view} />
|
||||
</CollapseGroup>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue