fix: relative time update and social media style

Signed-off-by: Innei <i@innei.in>
This commit is contained in:
Innei 2024-07-24 19:04:30 +08:00
parent ca2389deca
commit f2b6a2f50b
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
11 changed files with 126 additions and 48 deletions

View File

@ -0,0 +1,47 @@
import dayjs from "dayjs"
import type { FC } from "react"
import { useEffect, useState } from "react"
import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from "../tooltip"
const formatTime = (date: string | Date, relativeBeforeDay?: number) => {
if (
relativeBeforeDay &&
Math.abs(dayjs(date).diff(new Date(), "d")) > relativeBeforeDay
) {
return dayjs(date).format("lll")
}
return dayjs
.duration(dayjs(date).diff(dayjs(), "minute"), "minute")
.humanize()
}
export const RelativeTime: FC<{
date: string | Date
displayAbsoluteTimeAfterDay?: number
}> = (props) => {
const { displayAbsoluteTimeAfterDay = 29 } = props
const [relative, setRelative] = useState<string>(
formatTime(props.date, displayAbsoluteTimeAfterDay),
)
useEffect(() => {
setRelative(formatTime(props.date, displayAbsoluteTimeAfterDay))
let timer: any = setInterval(() => {
setRelative(formatTime(props.date, displayAbsoluteTimeAfterDay))
}, 1000)
return () => {
timer = clearInterval(timer)
}
}, [props.date, displayAbsoluteTimeAfterDay])
return (
<Tooltip>
<TooltipTrigger>{relative}</TooltipTrigger>
<TooltipPortal>
<TooltipContent>{dayjs(props.date).format("llll")}</TooltipContent>
</TooltipPortal>
</Tooltip>
)
}

View File

@ -4,6 +4,10 @@ import { repository } from "@pkg"
import { browserDB } from "@renderer/database"
import { CleanerService } from "@renderer/services/cleaner"
import { registerGlobalContext } from "@shared/bridge"
import dayjs from "dayjs"
import duration from "dayjs/plugin/duration"
import localizedFormat from "dayjs/plugin/localizedFormat"
import relativeTime from "dayjs/plugin/relativeTime"
import { enableMapSet } from "immer"
import { toast } from "sonner"
@ -41,6 +45,12 @@ export const initializeApp = async () => {
appLog(`${APP_NAME}: Next generation information browser`, repository.url)
appLog(`Initialize ${APP_NAME}...`)
window.version = APP_VERSION
// Initialize dayjs
dayjs.extend(duration)
dayjs.extend(relativeTime)
dayjs.extend(localizedFormat)
const now = Date.now()
enableMapSet()

View File

@ -1,8 +0,0 @@
import dayjs from "dayjs"
import duration from "dayjs/plugin/duration"
import relativeTime from "dayjs/plugin/relativeTime"
dayjs.extend(duration)
dayjs.extend(relativeTime)
export { default } from "dayjs"

View File

@ -1,11 +1,11 @@
import { FeedIcon } from "@renderer/components/feed-icon"
import { ReactVirtuosoItemPlaceholder } from "@renderer/components/ui/placeholder"
import { useAsRead } from "@renderer/hooks/biz/useAsRead"
import dayjs from "@renderer/lib/dayjs"
import { cn } from "@renderer/lib/utils"
import { EntryTranslation } from "@renderer/modules/entry-column/translation"
import { useEntry } from "@renderer/store/entry/hooks"
import { useFeedById } from "@renderer/store/feed"
import dayjs from "dayjs"
import { StarIcon } from "./star-icon"
import type { UniversalItemProps } from "./types"

View File

@ -22,7 +22,7 @@ import { AudioItem } from "./audio-item"
import { NotificationItem } from "./notification-item"
import { PictureItem } from "./picture-item"
import { SocialMediaItem } from "./social-media-item"
import type { UniversalItemProps } from "./types"
import type { EntryListItemFC } from "./types"
import { VideoItem } from "./video-item"
interface EntryItemProps {
@ -77,7 +77,7 @@ function EntryItemImpl({
leading: false,
},
)
let Item: FC<UniversalItemProps>
let Item: EntryListItemFC
switch (view) {
case FeedViewType.Articles: {
@ -151,10 +151,11 @@ function EntryItemImpl({
<div
className={cn(
"rounded-md bg-theme-background transition-colors",
isActive && "bg-theme-item-active",
isActive && "!bg-theme-item-active",
asRead ?
"text-zinc-700 dark:text-neutral-400" :
"text-zinc-900 dark:text-neutral-300",
Item.wrapperClassName,
)}
onClick={handleClick}
onMouseEnter={handleMouseEnter}

View File

@ -1,11 +1,9 @@
import { FeedIcon } from "@renderer/components/feed-icon"
import { RelativeTime } from "@renderer/components/ui/datetime"
import { Image } from "@renderer/components/ui/image"
import { FEED_COLLECTION_LIST } from "@renderer/constants"
import { useAsRead } from "@renderer/hooks/biz/useAsRead"
import {
useRouteParamsSelector,
} from "@renderer/hooks/biz/useRouteParams"
import dayjs from "@renderer/lib/dayjs"
import { useRouteParamsSelector } from "@renderer/hooks/biz/useRouteParams"
import { cn } from "@renderer/lib/utils"
import { EntryTranslation } from "@renderer/modules/entry-column/translation"
import { useEntry } from "@renderer/store/entry/hooks"
@ -38,6 +36,9 @@ export function ListItem({
// NOTE: prevent 0 height element, react virtuoso will not stop render any more
if (!entry || !feed) return <ReactVirtuosoItemPlaceholder />
const displayTime = inInCollection ?
entry.collections?.createdAt :
entry.entries.publishedAt
return (
<div
className={cn(
@ -51,23 +52,20 @@ export function ListItem({
<div
className={cn(
"flex gap-1 text-[10px] font-bold",
asRead ? "text-zinc-400 dark:text-neutral-500" : "text-zinc-500 dark:text-zinc-400",
asRead ?
"text-zinc-400 dark:text-neutral-500" :
"text-zinc-500 dark:text-zinc-400",
entry.collections && "text-zinc-600 dark:text-zinc-500",
)}
>
<span className="truncate">{feed.title}</span>
<span>·</span>
<span className="shrink-0">
{dayjs
.duration(
dayjs(
inInCollection ?
entry.collections?.createdAt :
entry.entries.publishedAt,
).diff(dayjs(), "minute"),
"minute",
)
.humanize()}
{!!displayTime && (
<RelativeTime
date={displayTime}
/>
)}
</span>
</div>
<div

View File

@ -1,8 +1,8 @@
import { FeedIcon } from "@renderer/components/feed-icon"
import { RelativeTime } from "@renderer/components/ui/datetime"
import { Image } from "@renderer/components/ui/image"
import { usePreviewImages } from "@renderer/components/ui/image/hooks"
import { useAsRead } from "@renderer/hooks/biz/useAsRead"
import dayjs from "@renderer/lib/dayjs"
import { cn } from "@renderer/lib/utils"
import { useEntry } from "@renderer/store/entry/hooks"
import { useFeedById } from "@renderer/store/feed"
@ -10,9 +10,13 @@ import { useFeedById } from "@renderer/store/feed"
import { ReactVirtuosoItemPlaceholder } from "../../components/ui/placeholder"
import { StarIcon } from "./star-icon"
import { EntryTranslation } from "./translation"
import type { UniversalItemProps } from "./types"
import type { EntryListItemFC } from "./types"
export function SocialMediaItem({ entryId, entryPreview, translation }: UniversalItemProps) {
export const SocialMediaItem: EntryListItemFC = ({
entryId,
entryPreview,
translation,
}) => {
const entry = useEntry(entryId) || entryPreview
const previewImage = usePreviewImages()
@ -21,28 +25,47 @@ export function SocialMediaItem({ entryId, entryPreview, translation }: Universa
// NOTE: prevent 0 height element, react virtuoso will not stop render any more
if (!entry || !feed) return <ReactVirtuosoItemPlaceholder />
return (
<div className={cn("relative flex w-full py-3 pl-3 pr-2", !asRead && "before:absolute before:-left-0.5 before:top-[22px] before:block before:size-2 before:rounded-full before:bg-theme-accent")}>
<FeedIcon feed={feed} entry={entry.entries} size={28} />
<div className="min-w-0 flex-1">
<div className={cn("-mt-0.5 flex-1 text-sm", entry.entries.description && "line-clamp-5")}>
<div
className={cn(
"relative flex py-3 pl-3 pr-2",
!asRead &&
// "before:absolute before:-left-4 before:top-[22px] before:block before:size-2 before:rounded-full before:bg-theme-accent",
"bg-theme-accent/15",
)}
>
<FeedIcon
className="mask-squircle mask"
feed={feed}
entry={entry.entries}
size={36}
/>
<div className="ml-2 min-w-0 flex-1">
<div
className={cn(
"-mt-0.5 flex-1 text-sm",
entry.entries.description && "line-clamp-5",
)}
>
<div className="space-x-1">
<span className="font-medium">{entry.entries.author}</span>
<span className="font-semibold">{entry.entries.author}</span>
<span className="text-zinc-500">·</span>
<span className="text-zinc-500">
{dayjs
.duration(
dayjs(entry.entries.publishedAt).diff(dayjs(), "minute"),
"minute",
)
.humanize()}
<RelativeTime date={entry.entries.publishedAt} />
</span>
</div>
<div className={cn("relative mt-0.5", !!entry.collections && "pr-5")}>
<EntryTranslation source={entry.entries.description} target={translation?.description} />
{!!entry.collections && (
<StarIcon />
<div
className={cn(
"relative mt-0.5 whitespace-pre-line text-base",
!!entry.collections && "pr-5",
)}
>
<EntryTranslation
source={entry.entries.description}
target={translation?.description}
/>
{!!entry.collections && <StarIcon />}
</div>
</div>
<div className="mt-1 flex gap-2 overflow-x-auto">
@ -67,3 +90,5 @@ export function SocialMediaItem({ entryId, entryPreview, translation }: Universa
</div>
)
}
SocialMediaItem.wrapperClassName = tw`w-[75ch] m-auto hover:bg-theme-item-hover`

View File

@ -1,4 +1,5 @@
import type { CombinedEntryModel, FeedModel } from "@renderer/models"
import type { FC } from "react"
export type UniversalItemProps = {
entryId: string
@ -11,3 +12,7 @@ export type UniversalItemProps = {
description?: string
} | null
}
export type EntryListItemFC<P extends object = object> = FC<P & UniversalItemProps> & {
wrapperClassName?: string
}

View File

@ -12,7 +12,6 @@ import {
import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry"
import { useRouteParamsSelector } from "@renderer/hooks/biz/useRouteParams"
import { useDeleteSubscription } from "@renderer/hooks/biz/useSubscriptionActions"
import dayjs from "@renderer/lib/dayjs"
import { nextFrame } from "@renderer/lib/dom"
import { showNativeMenu } from "@renderer/lib/native-menu"
import { cn } from "@renderer/lib/utils"
@ -20,6 +19,7 @@ import { getFeedById, useFeedById } from "@renderer/store/feed"
import { useSubscriptionByFeedId } from "@renderer/store/subscription"
import { useFeedUnreadStore } from "@renderer/store/unread"
import { WEB_URL } from "@shared/constants"
import dayjs from "dayjs"
import { memo, useCallback } from "react"
import { useFeedClaimModal } from "../claim/hooks"

View File

@ -2,10 +2,10 @@ import { useMe } from "@renderer/atoms/user"
import { Avatar, AvatarFallback, AvatarImage } from "@renderer/components/ui/avatar"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@renderer/components/ui/table"
import { Balance } from "@renderer/components/ui/wallet/balance"
import dayjs from "@renderer/lib/dayjs"
import { cn } from "@renderer/lib/utils"
import { SettingSectionTitle } from "@renderer/modules/settings/section"
import { useWallet, useWalletTransactions } from "@renderer/queries/wallet"
import dayjs from "dayjs"
export const TransactionsSection = () => {
const user = useMe()

View File

@ -51,7 +51,7 @@ export const TipModalContent: FC<{
if (transactionsQuery.isPending || myWallet.isPending) {
return (
<div className="center h-32 w-[650px]">
<div className="center h-32 w-[350px]">
<LoadingCircle size="large" />
</div>
)