feat: optimize daily report and report modal for social media

Signed-off-by: Innei <i@innei.in>
This commit is contained in:
Innei 2024-08-07 13:55:31 +08:00
parent a625d04328
commit 252962c188
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
9 changed files with 409 additions and 293 deletions

View File

@ -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

View File

@ -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<

View File

@ -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,

View File

@ -18,4 +18,4 @@ export const modalMontionConfig = {
transition: microReboundPreset,
}
export const MODAL_STACK_Z_INDEX = 100
export const MODAL_STACK_Z_INDEX = 40

View File

@ -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>
)
}

View File

@ -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>
)
}

View File

@ -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>
)
}
})

View File

@ -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,

View File

@ -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>
)