fix: relative time update and social media style
Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
ca2389deca
commit
f2b6a2f50b
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue