From 2f9b224866c84ad02da696118d8a9245dbd65faa Mon Sep 17 00:00:00 2001 From: DIYgod Date: Sat, 8 Apr 2023 14:43:11 +0100 Subject: [PATCH] feat: hot list in activities --- data/showcase.json | 2 +- data/topics.json | 14 ++- public/locales/zh/dashboard.json | 7 +- src/components/main/MainFeed.tsx | 39 ++++++++- src/lib/types.ts | 4 + src/models/home.model.ts | 142 ++++++++++++++++++++++++------- src/pages/activities.tsx | 9 +- src/queries/home.server.ts | 2 +- src/queries/home.ts | 3 +- 9 files changed, 181 insertions(+), 41 deletions(-) diff --git a/data/showcase.json b/data/showcase.json index d4f77257..20a782f8 100644 --- a/data/showcase.json +++ b/data/showcase.json @@ -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 ] diff --git a/data/topics.json b/data/topics.json index c200e812..ee02e825 100644 --- a/data/topics.json +++ b/data/topics.json @@ -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" ] } ] diff --git a/public/locales/zh/dashboard.json b/public/locales/zh/dashboard.json index 3eb9975b..ec768302 100644 --- a/public/locales/zh/dashboard.json +++ b/public/locales/zh/dashboard.json @@ -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": "全部" } \ No newline at end of file diff --git a/src/components/main/MainFeed.tsx b/src/components/main/MainFeed.tsx index cb4b7068..6abe11b3 100644 --- a/src/components/main/MainFeed.tsx +++ b/src/components/main/MainFeed.tsx @@ -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 = ({ ))} )} + {post.stat?.viewDetailCount && ( + + + {post.stat?.viewDetailCount} + + )}
= ({ 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 ( <>
)} + {type === "hot" && ( + + )} {feed.isLoading ? (
{t("Loading")}...
) : !feed.data?.pages[0]?.count ? ( diff --git a/src/lib/types.ts b/src/lib/types.ts index 63a7df6f..26fd7996 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -103,6 +103,10 @@ export type ExpandedNote = NoteEntity & { } } } + stat?: { + viewDetailCount: number + hotScore?: number + } } export type Notes = { diff --git a/src/models/home.model.ts b/src/models/home.model.ts index 73cf04c3..2a0b9450 100644 --- a/src/models/home.model.ts +++ b/src/models/home.model.ts @@ -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, + } + } } } diff --git a/src/pages/activities.tsx b/src/pages/activities.tsx index b972ea2b..588a66bc 100644 --- a/src/pages/activities.tsx +++ b/src/pages/activities.tsx @@ -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("latest") const tabs = [ { text: "Latest", onClick: () => setFeedType("latest"), active: feedType === "latest", }, + { + text: "Hottest", + onClick: () => setFeedType("hot"), + active: feedType === "hot", + }, { text: "Following", onClick: () => { diff --git a/src/queries/home.server.ts b/src/queries/home.server.ts index a1a8f37d..370a1f2f 100644 --- a/src/queries/home.server.ts +++ b/src/queries/home.server.ts @@ -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[] diff --git a/src/queries/home.ts b/src/queries/home.ts index 39a1b285..c080a2a7 100644 --- a/src/queries/home.ts +++ b/src/queries/home.ts @@ -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],