mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-19 03:09:14 +08:00
feat(ui): show diff position when refresh
This commit is contained in:
parent
47767a6d62
commit
b14cb9ea1f
@ -93,6 +93,7 @@ export interface NewsItem {
|
|||||||
hover?: string
|
hover?: string
|
||||||
date?: number | string
|
date?: number | string
|
||||||
info?: false | string
|
info?: false | string
|
||||||
|
diff?: number
|
||||||
icon?: false | string | {
|
icon?: false | string | {
|
||||||
url: string
|
url: string
|
||||||
scale: number
|
scale: number
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import type { NewsItem, SourceID, SourceResponse } from "@shared/types"
|
import type { NewsItem, SourceID, SourceResponse } from "@shared/types"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useInView } from "framer-motion"
|
import { AnimatePresence, motion, useInView } from "framer-motion"
|
||||||
import { useAtom } from "jotai"
|
import { useAtom } from "jotai"
|
||||||
import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from "react"
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"
|
||||||
import { sources } from "@shared/sources"
|
import { sources } from "@shared/sources"
|
||||||
import type { SyntheticListenerMap } from "@dnd-kit/core/dist/hooks/utilities"
|
import type { SyntheticListenerMap } from "@dnd-kit/core/dist/hooks/utilities"
|
||||||
import { ofetch } from "ofetch"
|
import { ofetch } from "ofetch"
|
||||||
@ -54,6 +54,7 @@ export const CardWrapper = forwardRef<HTMLDivElement, ItemsProps>(({ id, isDragg
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const prevSourceItems: Partial<Record<SourceID, NewsItem[]>> = {}
|
||||||
function NewsCard({ id, inView, handleListeners }: NewsCardProps) {
|
function NewsCard({ id, inView, handleListeners }: NewsCardProps) {
|
||||||
const [focusSources, setFocusSources] = useAtom(focusSourcesAtom)
|
const [focusSources, setFocusSources] = useAtom(focusSourcesAtom)
|
||||||
const [refetchSource, setRefetchSource] = useAtom(refetchSourcesAtom)
|
const [refetchSource, setRefetchSource] = useAtom(refetchSourcesAtom)
|
||||||
@ -72,10 +73,28 @@ function NewsCard({ id, inView, handleListeners }: NewsCardProps) {
|
|||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
headers,
|
headers,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (response.items && sources[_id].type === "hottest" && prevSourceItems[_id]) {
|
||||||
|
response.items.forEach((item, i) => {
|
||||||
|
const o = prevSourceItems[_id]!.findIndex(k => k.id === item.id)
|
||||||
|
item.extra = {
|
||||||
|
...item?.extra,
|
||||||
|
diff: o === -1 ? undefined : o - i,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
},
|
},
|
||||||
// refetch 时显示原有的数据
|
// refetch 时显示原有的数据
|
||||||
placeholderData: prev => prev,
|
placeholderData: (prev) => {
|
||||||
|
if (prev?.items && sources[id].type === "hottest") prevSourceItems[id] = prev.items
|
||||||
|
return prev
|
||||||
|
},
|
||||||
staleTime: 1000 * 60 * 5,
|
staleTime: 1000 * 60 * 5,
|
||||||
enabled: inView,
|
enabled: inView,
|
||||||
})
|
})
|
||||||
@ -162,6 +181,31 @@ function UpdatedTime({ isError, updatedTime }: { updatedTime: any, isError: bool
|
|||||||
return "加载中..."
|
return "加载中..."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DiffNumber({ diff }: { diff: number }) {
|
||||||
|
const [shown, setShown] = useState(true)
|
||||||
|
useEffect(() => {
|
||||||
|
setShown(true)
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setShown(false)
|
||||||
|
}, 5000)
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}, [setShown, diff])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
{ shown && (
|
||||||
|
<motion.span
|
||||||
|
initial={{ opacity: 0, y: -15 }}
|
||||||
|
animate={{ opacity: 0.5, y: -7 }}
|
||||||
|
exit={{ opacity: 0, y: -15 }}
|
||||||
|
className={clsx("absolute left-0 text-xs", diff < 0 ? "text-green" : "text-red")}
|
||||||
|
>
|
||||||
|
{diff > 0 ? `+${diff}` : diff}
|
||||||
|
</motion.span>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
)
|
||||||
|
}
|
||||||
function ExtraInfo({ item }: { item: NewsItem }) {
|
function ExtraInfo({ item }: { item: NewsItem }) {
|
||||||
if (item?.extra?.info) {
|
if (item?.extra?.info) {
|
||||||
return <>{item.extra.info}</>
|
return <>{item.extra.info}</>
|
||||||
@ -196,13 +240,14 @@ function NewsListHot({ items }: { items: NewsItem[] }) {
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
title={item.extra?.hover}
|
title={item.extra?.hover}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"flex gap-2 items-center mb-2 items-stretch",
|
"flex gap-2 items-center mb-2 items-stretch relative",
|
||||||
"hover:bg-neutral-400/10 rounded-md pr-1 visited:(text-neutral-400)",
|
"hover:bg-neutral-400/10 rounded-md pr-1 visited:(text-neutral-400)",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className={clsx("bg-neutral-400/10 min-w-6 flex justify-center items-center rounded-md text-sm")}>
|
<span className={clsx("bg-neutral-400/10 min-w-6 flex justify-center items-center rounded-md text-sm")}>
|
||||||
{i + 1}
|
{i + 1}
|
||||||
</span>
|
</span>
|
||||||
|
{!!item.extra?.diff && <DiffNumber diff={item.extra.diff} />}
|
||||||
<span className="self-start line-height-none">
|
<span className="self-start line-height-none">
|
||||||
<span className="mr-2 text-base">
|
<span className="mr-2 text-base">
|
||||||
{item.title}
|
{item.title}
|
||||||
|
@ -50,4 +50,4 @@ button:disabled {
|
|||||||
|
|
||||||
#dropdown-menu li {
|
#dropdown-menu li {
|
||||||
--at-apply: hover:bg-neutral-400/10 rounded-md flex items-center p-1 gap-1;
|
--at-apply: hover:bg-neutral-400/10 rounded-md flex items-center p-1 gap-1;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user