diff --git a/src/app/(home)/(activities)/latest/page.tsx b/src/app/(home)/(activities)/latest/page.tsx new file mode 100644 index 00000000..b03a4036 --- /dev/null +++ b/src/app/(home)/(activities)/latest/page.tsx @@ -0,0 +1,30 @@ +import { Metadata } from "next" + +import { Hydrate, dehydrate } from "@tanstack/react-query" + +import { HomeFeed } from "~/components/home/HomeFeed" +import { APP_NAME } from "~/lib/env" +import getQueryClient from "~/lib/query-client" +import { prefetchGetFeed } from "~/queries/home.server" + +export const metadata: Metadata = { + title: `Latest Activities - ${APP_NAME}`, +} + +export default async function LatestActivities() { + const queryClient = getQueryClient() + await prefetchGetFeed( + { + type: "latest", + }, + queryClient, + ) + + const dehydratedState = dehydrate(queryClient) + + return ( + + + + ) +} diff --git a/src/app/(home)/(activities)/page.tsx b/src/app/(home)/(activities)/page.tsx index b03a4036..cb870107 100644 --- a/src/app/(home)/(activities)/page.tsx +++ b/src/app/(home)/(activities)/page.tsx @@ -3,19 +3,19 @@ import { Metadata } from "next" import { Hydrate, dehydrate } from "@tanstack/react-query" import { HomeFeed } from "~/components/home/HomeFeed" -import { APP_NAME } from "~/lib/env" +import { APP_NAME, APP_SLOGAN } from "~/lib/env" import getQueryClient from "~/lib/query-client" import { prefetchGetFeed } from "~/queries/home.server" export const metadata: Metadata = { - title: `Latest Activities - ${APP_NAME}`, + title: `${APP_NAME} - ${APP_SLOGAN}`, } -export default async function LatestActivities() { +export default async function HomeActivities() { const queryClient = getQueryClient() await prefetchGetFeed( { - type: "latest", + type: "featured", }, queryClient, ) @@ -24,7 +24,7 @@ export default async function LatestActivities() { return ( - + ) } diff --git a/src/components/home/HomeActivitiesTabs.tsx b/src/components/home/HomeActivitiesTabs.tsx index 980c68d4..2ccb01ca 100644 --- a/src/components/home/HomeActivitiesTabs.tsx +++ b/src/components/home/HomeActivitiesTabs.tsx @@ -16,10 +16,15 @@ export const HomeActivitiesTabs = () => { const tabs = [ { - text: "Latest", + text: "Featured", href: "/", active: pathname === "/", }, + { + text: "Latest", + href: "/latest", + active: pathname === "/latest", + }, { text: "Hottest", href: "/hottest", diff --git a/src/lib/i18n/locales/zh/dashboard.json b/src/lib/i18n/locales/zh/dashboard.json index 0e902a7c..3552d51f 100644 --- a/src/lib/i18n/locales/zh/dashboard.json +++ b/src/lib/i18n/locales/zh/dashboard.json @@ -181,6 +181,7 @@ "Subdomain Available.": "子域名可用。", "Subdomain Unavailable.": "子域名不可用。", "Recheck": "重新检查", + "Featured": "精选", "Hottest": "最热", "Latest": "最新", "Following": "关注", diff --git a/src/models/home.model.ts b/src/models/home.model.ts index ba8a0fcb..d2b88323 100644 --- a/src/models/home.model.ts +++ b/src/models/home.model.ts @@ -8,6 +8,7 @@ import { ExpandedNote } from "~/lib/types" import { client } from "~/queries/graphql" import filter from "../../data/filter.json" +import titles from "../../data/titles.json" import topics from "../../data/topics.json" export type FeedType = @@ -18,12 +19,14 @@ export type FeedType = | "search" | "tag" | "comments" + | "featured" + export type SearchType = "latest" | "hottest" export async function getFeed({ type, cursor, - limit = 30, + limit = 12, characterId, daysInterval, searchKeyword, @@ -480,9 +483,9 @@ export async function getFeed({ }, }, take: 40, - orderBy: { - stat: { - viewDetailCount: desc + orderBy: { + stat: { + viewDetailCount: desc } }, ) { @@ -534,6 +537,102 @@ export async function getFeed({ count: list?.length || 0, } } + case "featured": { + const result = await client + .query( + gql` + query getNotes($filter: [Int!], $limit: Int, $whitelist: [Int!]) { + notes( + where: { + characterId: { + notIn: $filter + }, + deleted: { + equals: false, + }, + metadata: { + content: { + path: "sources", + array_contains: "xlog" + }, + NOT: [{ + content: { + path: "tags", + array_starts_with: "comment" + } + }] + }, + OR: [ + { + stat: { + viewDetailCount: { + gt: 30 + }, + }, + }, + { + characterId: { + in: $whitelist + }, + } + ] + }, + orderBy: [{ createdAt: desc }], + take: $limit, + ${cursorQuery} + ) { + stat { + viewDetailCount + } + ${resultFields} + } + } + `, + { + filter: filter.latest, + limit, + whitelist: titles.reduce((acc, cur) => { + acc = acc.concat(cur.list) + return acc + }, [] as number[]), + }, + ) + .toPromise() + + let list = await Promise.all( + result?.data?.notes.map(async (page: any) => { + const secondAgo = dayjs().diff(dayjs(page.createdAt), "second") + page.stat.hotScore = + page.stat.viewDetailCount / Math.max(Math.log10(secondAgo), 1) + + const expand = await expandCrossbellNote({ + note: page, + useHTML, + }) + delete expand.metadata?.content.content + return expand + }), + ) + + const cursor = list?.length + ? `${list[list.length - 1]?.characterId}_${list[list.length - 1] + ?.noteId}` + : undefined + + list = list.sort((a, b) => { + if (a.stat?.hotScore && b.stat?.hotScore) { + return b.stat.hotScore - a.stat.hotScore + } else { + return 0 + } + }) + + return { + list, + cursor, + count: list?.length || 0, + } + } case "search": { const result = await indexer.search.notes(searchKeyword!, { sources: ["xlog"],