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