feat: hot list in activities
This commit is contained in:
parent
2869daa86b
commit
2f9b224866
|
|
@ -10,5 +10,5 @@
|
|||
43625, 42787, 43258, 33462, 47109, 46939, 45701, 33233, 37346, 40671, 51552,
|
||||
51585, 48937, 51379, 51460, 51084, 51509, 51478, 51639, 48910, 51139, 39329,
|
||||
51683, 51077, 51374, 49120, 47356, 17096, 50708, 51430, 51935, 51756, 6558,
|
||||
49392, 52186, 52055, 52282, 52270, 5825, 51351, 52233, 51308
|
||||
49392, 52186, 52055, 52282, 52270, 5825, 51351, 52233, 51308, 52388, 52280
|
||||
]
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@
|
|||
"52220-2",
|
||||
"52387-1",
|
||||
"52314-1",
|
||||
"52336-2"
|
||||
"52336-2",
|
||||
"51478-10",
|
||||
"33470-7"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -134,7 +136,8 @@
|
|||
"52388-2",
|
||||
"52242-16",
|
||||
"51084-7",
|
||||
"50708-12"
|
||||
"50708-12",
|
||||
"52387-2"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -262,7 +265,9 @@
|
|||
"52055-24",
|
||||
"52055-21",
|
||||
"51903-2",
|
||||
"51683-125"
|
||||
"51683-125",
|
||||
"46517-82",
|
||||
"51803-3"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -285,7 +290,8 @@
|
|||
"31132-7",
|
||||
"31132-5",
|
||||
"48910-9",
|
||||
"48910-11"
|
||||
"48910-11",
|
||||
"51124-31"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@
|
|||
"DNS check passed.": "DNS 检查通过。",
|
||||
"DNS check failed.": "DNS 检查失败。",
|
||||
"Recheck": "重新检查",
|
||||
"Hottest": "最热",
|
||||
"Latest": "最新",
|
||||
"Following": "关注",
|
||||
"Create a Post": "创建文章",
|
||||
|
|
@ -176,5 +177,9 @@
|
|||
"Participate in the development of xLog": "参与 xLog 的开发",
|
||||
"Follow xLog's Twitter": "关注 xLog 的 Twitter",
|
||||
"Check out the updates of other bloggers": "查看其他博主的更新",
|
||||
"You have already imported them, please enter the post page to create a new post!": "您已经导入了它们,请进入文章页面创建新的文章吧。"
|
||||
"You have already imported them, please enter the post page to create a new post!": "您已经导入了它们,请进入文章页面创建新的文章吧。",
|
||||
"Today": "今天",
|
||||
"This month": "本月",
|
||||
"This week": "本周",
|
||||
"All time": "全部"
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import { useRouter } from "next/router"
|
|||
import { Image } from "~/components/ui/Image"
|
||||
import { useTranslation } from "next-i18next"
|
||||
import { useGetFeed } from "~/queries/home"
|
||||
import type { FeedType } from "~/models/home.model"
|
||||
import { CharacterFloatCard } from "~/components/common/CharacterFloatCard"
|
||||
import { useAccountState } from "@crossbell/connect-kit"
|
||||
import InfiniteScroll from "react-infinite-scroller"
|
||||
|
|
@ -14,6 +15,7 @@ import { Switch } from "@headlessui/react"
|
|||
import { setStorage, getStorage } from "~/lib/storage"
|
||||
import { Tooltip } from "~/components/ui/Tooltip"
|
||||
import { Titles } from "~/components/common/Titles"
|
||||
import { Tabs } from "~/components/ui/Tabs"
|
||||
|
||||
const Post = ({
|
||||
post,
|
||||
|
|
@ -103,6 +105,12 @@ const Post = ({
|
|||
))}
|
||||
</span>
|
||||
)}
|
||||
{post.stat?.viewDetailCount && (
|
||||
<span className="xlog-post-views inline-flex items-center">
|
||||
<i className="i-mingcute:eye-line mr-[2px]" />
|
||||
<span>{post.stat?.viewDetailCount}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="xlog-post-excerpt mt-3 text-zinc-500 line-clamp-2"
|
||||
|
|
@ -130,7 +138,7 @@ const Post = ({
|
|||
}
|
||||
|
||||
export const MainFeed: React.FC<{
|
||||
type?: "latest" | "recommend" | "following" | "topic"
|
||||
type?: FeedType
|
||||
noteIds?: string[]
|
||||
}> = ({ type, noteIds }) => {
|
||||
const { t } = useTranslation(["common", "site"])
|
||||
|
|
@ -139,10 +147,13 @@ export const MainFeed: React.FC<{
|
|||
(s) => s.computed.account?.characterId,
|
||||
)
|
||||
|
||||
const [hotInterval, setHotInterval] = useState(7)
|
||||
|
||||
const feed = useGetFeed({
|
||||
type: type,
|
||||
characterId: currentCharacterId,
|
||||
noteIds: noteIds,
|
||||
daysInterval: hotInterval,
|
||||
})
|
||||
|
||||
const hasFiltering = type === "latest"
|
||||
|
|
@ -153,6 +164,29 @@ export const MainFeed: React.FC<{
|
|||
setAiFiltering(getStorage("ai_filtering")?.enabled || true)
|
||||
}, [])
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
text: "Today",
|
||||
onClick: () => setHotInterval(1),
|
||||
active: hotInterval === 1,
|
||||
},
|
||||
{
|
||||
text: "This week",
|
||||
onClick: () => setHotInterval(7),
|
||||
active: hotInterval === 7,
|
||||
},
|
||||
{
|
||||
text: "This month",
|
||||
onClick: () => setHotInterval(30),
|
||||
active: hotInterval === 30,
|
||||
},
|
||||
{
|
||||
text: "All time",
|
||||
onClick: () => setHotInterval(0),
|
||||
active: hotInterval === 0,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<InfiniteScroll
|
||||
|
|
@ -202,6 +236,9 @@ export const MainFeed: React.FC<{
|
|||
</Switch>
|
||||
</div>
|
||||
)}
|
||||
{type === "hot" && (
|
||||
<Tabs items={tabs} className="border-none text-sm -my-4"></Tabs>
|
||||
)}
|
||||
{feed.isLoading ? (
|
||||
<div className="text-center text-zinc-600">{t("Loading")}...</div>
|
||||
) : !feed.data?.pages[0]?.count ? (
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ export type ExpandedNote = NoteEntity & {
|
|||
}
|
||||
}
|
||||
}
|
||||
stat?: {
|
||||
viewDetailCount: number
|
||||
hotScore?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type Notes = {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { indexer } from "@crossbell/indexer"
|
|||
import { toCid } from "~/lib/ipfs-parser"
|
||||
import { createClient, cacheExchange, fetchExchange } from "@urql/core"
|
||||
import { SITE_URL, SCORE_API_DOMAIN } from "~/lib/env"
|
||||
import dayjs from "dayjs"
|
||||
|
||||
const expandPage = async (
|
||||
page: ExpandedNote,
|
||||
|
|
@ -57,21 +58,25 @@ const expandPage = async (
|
|||
return page
|
||||
}
|
||||
|
||||
export type FeedType = "latest" | "following" | "topic" | "hot"
|
||||
|
||||
export async function getFeed({
|
||||
type,
|
||||
cursor,
|
||||
limit = 10,
|
||||
characterId,
|
||||
noteIds,
|
||||
daysInterval,
|
||||
}: {
|
||||
type?: "latest" | "recommend" | "following" | "topic"
|
||||
type?: FeedType
|
||||
cursor?: string
|
||||
limit?: number
|
||||
characterId?: number
|
||||
noteIds?: string[]
|
||||
daysInterval?: number
|
||||
}) {
|
||||
switch (type) {
|
||||
case "latest":
|
||||
case "latest": {
|
||||
const result = await indexer.getNotes({
|
||||
sources: "xlog",
|
||||
tags: ["post"],
|
||||
|
|
@ -91,7 +96,8 @@ export async function getFeed({
|
|||
cursor: result.cursor,
|
||||
count: result.count,
|
||||
}
|
||||
case "following":
|
||||
}
|
||||
case "following": {
|
||||
if (!characterId) {
|
||||
return {
|
||||
list: [],
|
||||
|
|
@ -119,7 +125,8 @@ export async function getFeed({
|
|||
count: result.count,
|
||||
}
|
||||
}
|
||||
case "topic":
|
||||
}
|
||||
case "topic": {
|
||||
if (!noteIds) {
|
||||
return {
|
||||
list: [],
|
||||
|
|
@ -140,48 +147,123 @@ export async function getFeed({
|
|||
} }, characterId: { equals: ${note.split("-")[0]}}},`,
|
||||
)
|
||||
.join("\n")
|
||||
const topicResult = await client
|
||||
const result = await client
|
||||
.query(
|
||||
`
|
||||
query getNotes {
|
||||
notes(
|
||||
where: {
|
||||
OR: [
|
||||
${orString}
|
||||
]
|
||||
},
|
||||
orderBy: [{ createdAt: desc }],
|
||||
take: 1000,
|
||||
) {
|
||||
characterId
|
||||
noteId
|
||||
character {
|
||||
handle
|
||||
query getNotes {
|
||||
notes(
|
||||
where: {
|
||||
OR: [
|
||||
${orString}
|
||||
]
|
||||
},
|
||||
orderBy: [{ createdAt: desc }],
|
||||
take: 1000,
|
||||
) {
|
||||
characterId
|
||||
noteId
|
||||
character {
|
||||
handle
|
||||
metadata {
|
||||
content
|
||||
}
|
||||
}
|
||||
createdAt
|
||||
metadata {
|
||||
uri
|
||||
content
|
||||
}
|
||||
}
|
||||
createdAt
|
||||
metadata {
|
||||
uri
|
||||
content
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}`,
|
||||
{},
|
||||
)
|
||||
.toPromise()
|
||||
|
||||
const topicList = await Promise.all(
|
||||
topicResult?.data?.notes.map(async (page: any) => {
|
||||
const list = await Promise.all(
|
||||
result?.data?.notes.map(async (page: any) => {
|
||||
return await expandPage(page)
|
||||
}),
|
||||
)
|
||||
|
||||
return {
|
||||
list: topicList,
|
||||
list: list,
|
||||
cursor: "",
|
||||
count: topicList?.length || 0,
|
||||
count: list?.length || 0,
|
||||
}
|
||||
}
|
||||
case "hot": {
|
||||
const client = createClient({
|
||||
url: "https://indexer.crossbell.io/v1/graphql",
|
||||
exchanges: [cacheExchange, fetchExchange],
|
||||
})
|
||||
|
||||
let time
|
||||
if (daysInterval) {
|
||||
time = dayjs().subtract(daysInterval, "day").toISOString()
|
||||
}
|
||||
|
||||
const result = await client
|
||||
.query(
|
||||
`
|
||||
query getNotes {
|
||||
notes(
|
||||
where: {
|
||||
${time ? `createdAt: { gt: "${time}" },` : ``}
|
||||
stat: { is: { viewDetailCount: { gt: 0 } } },
|
||||
metadata: { is: { content: { path: "sources", array_contains: "xlog" }, AND: { content: { path: "tags", array_contains: "post" } } } }
|
||||
},
|
||||
orderBy: { stat: { viewDetailCount: desc } },
|
||||
take: 50,
|
||||
) {
|
||||
stat {
|
||||
viewDetailCount
|
||||
}
|
||||
characterId
|
||||
noteId
|
||||
character {
|
||||
handle
|
||||
metadata {
|
||||
content
|
||||
}
|
||||
}
|
||||
createdAt
|
||||
metadata {
|
||||
uri
|
||||
content
|
||||
}
|
||||
}
|
||||
}`,
|
||||
{},
|
||||
)
|
||||
.toPromise()
|
||||
|
||||
let list: ExpandedNote[] = await Promise.all(
|
||||
result?.data?.notes.map(async (page: any) => {
|
||||
if (daysInterval) {
|
||||
const secondAgo = dayjs().diff(dayjs(page.createdAt), "second")
|
||||
page.stat.hotScore =
|
||||
page.stat.viewDetailCount / Math.max(Math.log10(secondAgo), 1)
|
||||
}
|
||||
|
||||
return await expandPage(page)
|
||||
}),
|
||||
)
|
||||
|
||||
if (daysInterval) {
|
||||
list = list.sort((a, b) => {
|
||||
if (a.stat?.hotScore && b.stat?.hotScore) {
|
||||
return b.stat.hotScore - a.stat.hotScore
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
list: list,
|
||||
cursor: "",
|
||||
count: list?.length || 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useAccountState, useConnectModal } from "@crossbell/connect-kit"
|
|||
import { dehydrate, QueryClient } from "@tanstack/react-query"
|
||||
import { prefetchGetSites } from "~/queries/site.server"
|
||||
import showcase from "../../data/showcase.json"
|
||||
import { useAccountSites } from "~/queries/site"
|
||||
import type { FeedType } from "~/models/home.model"
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations"
|
||||
import { languageDetector } from "~/lib/language-detector"
|
||||
import { MainFeed } from "~/components/main/MainFeed"
|
||||
|
|
@ -41,13 +41,18 @@ function Activities() {
|
|||
)
|
||||
const connectModal = useConnectModal()
|
||||
|
||||
const [feedType, setFeedType] = useState<"latest" | "following">("latest")
|
||||
const [feedType, setFeedType] = useState<FeedType>("latest")
|
||||
const tabs = [
|
||||
{
|
||||
text: "Latest",
|
||||
onClick: () => setFeedType("latest"),
|
||||
active: feedType === "latest",
|
||||
},
|
||||
{
|
||||
text: "Hottest",
|
||||
onClick: () => setFeedType("hot"),
|
||||
active: feedType === "hot",
|
||||
},
|
||||
{
|
||||
text: "Following",
|
||||
onClick: () => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import * as homeModel from "~/models/home.model"
|
|||
|
||||
export const prefetchGetFeed = async (
|
||||
data: {
|
||||
type?: "latest" | "recommend" | "following" | "topic"
|
||||
type?: homeModel.FeedType
|
||||
characterId?: number
|
||||
limit?: number
|
||||
noteIds?: string[]
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ import { useInfiniteQuery, useQuery } from "@tanstack/react-query"
|
|||
import * as homeModel from "~/models/home.model"
|
||||
|
||||
export const useGetFeed = (data?: {
|
||||
type?: "latest" | "recommend" | "following" | "topic"
|
||||
type?: homeModel.FeedType
|
||||
characterId?: number
|
||||
limit?: number
|
||||
noteIds?: string[]
|
||||
daysInterval?: number
|
||||
}) => {
|
||||
return useInfiniteQuery({
|
||||
queryKey: ["getFeed", data],
|
||||
|
|
|
|||
Loading…
Reference in New Issue