feat: hot list in activities

This commit is contained in:
DIYgod 2023-04-08 14:43:11 +01:00
parent 2869daa86b
commit 2f9b224866
No known key found for this signature in database
9 changed files with 181 additions and 41 deletions

View File

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

View File

@ -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"
]
}
]

View File

@ -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": "全部"
}

View File

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

View File

@ -103,6 +103,10 @@ export type ExpandedNote = NoteEntity & {
}
}
}
stat?: {
viewDetailCount: number
hotScore?: number
}
}
export type Notes = {

View File

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

View File

@ -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: () => {

View File

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

View File

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