172 lines
5.6 KiB
TypeScript
172 lines
5.6 KiB
TypeScript
import type { Metadata } from "next";
|
|
import Link from "next/link";
|
|
import {
|
|
ASR_CONTENT_TAGS,
|
|
isAsrContentTag,
|
|
resolveEffectiveContentTags,
|
|
} from "@/lib/asr-tags";
|
|
import { getFileUrl } from "@/lib/minio";
|
|
import { prisma } from "@/lib/prisma";
|
|
import FeedMasonry from "./components/FeedMasonry";
|
|
import SearchBox from "./components/SearchBox";
|
|
import type { FeedItem } from "./types/feed";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export const metadata: Metadata = {
|
|
title: "作品集 - 抖歪",
|
|
description: "抖歪作品集,记录当下时代的精彩瞬间",
|
|
};
|
|
|
|
export default async function Home({
|
|
searchParams,
|
|
}: {
|
|
searchParams?: { tag?: string };
|
|
}) {
|
|
const requestedTag = searchParams?.tag?.trim() || "";
|
|
const selectedTag =
|
|
requestedTag && isAsrContentTag(requestedTag) ? requestedTag : null;
|
|
|
|
const [videos, posts] = await Promise.all([
|
|
prisma.video.findMany({
|
|
where: selectedTag
|
|
? {
|
|
OR: [
|
|
{ manual_content_tags: { has: selectedTag } },
|
|
{
|
|
manual_content_tags: { isEmpty: true },
|
|
transcript: { is: { content_tags: { has: selectedTag } } },
|
|
},
|
|
],
|
|
}
|
|
: undefined,
|
|
orderBy: { created_at: "desc" },
|
|
take: 60,
|
|
include: {
|
|
author: true,
|
|
transcript: { select: { content_tags: true } },
|
|
},
|
|
}),
|
|
selectedTag
|
|
? Promise.resolve([])
|
|
: prisma.imagePost.findMany({
|
|
orderBy: { created_at: "desc" },
|
|
take: 60,
|
|
include: {
|
|
author: true,
|
|
images: { orderBy: { order: "asc" }, take: 1 },
|
|
},
|
|
}),
|
|
]);
|
|
|
|
const feed: FeedItem[] = [
|
|
...videos.map((v) => ({
|
|
type: "video" as const,
|
|
aweme_id: v.aweme_id,
|
|
created_at: v.created_at,
|
|
desc: v.desc,
|
|
video_url: getFileUrl(v.video_url),
|
|
cover_url: getFileUrl(v.cover_url ?? ""),
|
|
width: v.width ?? null,
|
|
height: v.height ?? null,
|
|
author: {
|
|
nickname: v.author.nickname,
|
|
avatar_url: getFileUrl(v.author.avatar_url ?? ""),
|
|
sec_uid: v.author.sec_uid,
|
|
},
|
|
likes: Number(v.digg_count),
|
|
content_tags: resolveEffectiveContentTags(
|
|
v.manual_content_tags,
|
|
v.transcript?.content_tags,
|
|
),
|
|
})),
|
|
...posts.map((p) => {
|
|
const coverPath = p.images?.[0]?.url;
|
|
return {
|
|
type: "image" as const,
|
|
aweme_id: p.aweme_id,
|
|
created_at: p.created_at,
|
|
desc: p.desc,
|
|
cover_url: coverPath ? getFileUrl(coverPath) : null,
|
|
width: p.images?.[0]?.width ?? null,
|
|
height: p.images?.[0]?.height ?? null,
|
|
author: {
|
|
nickname: p.author.nickname,
|
|
avatar_url: getFileUrl(p.author.avatar_url ?? ""),
|
|
sec_uid: p.author.sec_uid,
|
|
},
|
|
likes: Number(p.digg_count),
|
|
};
|
|
}),
|
|
]
|
|
//.sort(() => Math.random() - 0.5)
|
|
.sort((a, b) => +new Date(b.created_at) - +new Date(a.created_at));
|
|
|
|
return (
|
|
<main className="min-h-screen w-full bg-gray-50 dark:bg-black transition-colors duration-300">
|
|
<div className="relative pt-16 pb-12 px-4 sm:px-6 lg:px-8 flex flex-col items-center">
|
|
{/* 装饰背景 */}
|
|
<div className="absolute inset-0 -z-10 overflow-hidden pointer-events-none">
|
|
<div className="absolute left-[50%] top-0 h-[40rem] w-[40rem] -translate-x-1/2 -translate-y-1/2 rounded-full bg-gradient-to-b from-indigo-500/20 to-transparent blur-3xl dark:from-indigo-500/10" />
|
|
</div>
|
|
|
|
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-center mb-8 bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-600 dark:from-white dark:to-gray-400">
|
|
Douyin Archive
|
|
</h1>
|
|
|
|
<div className="w-full max-w-xl mb-12">
|
|
<SearchBox tag={selectedTag} />
|
|
</div>
|
|
|
|
<div className="mb-8 flex w-full max-w-4xl flex-wrap justify-center gap-2">
|
|
<Link
|
|
href="/"
|
|
className={`rounded-full border px-3 py-1.5 text-sm font-medium transition-colors ${
|
|
selectedTag
|
|
? "border-zinc-200 bg-white/70 text-zinc-600 hover:border-zinc-300 dark:border-white/10 dark:bg-white/5 dark:text-zinc-300"
|
|
: "border-blue-500 bg-blue-600 text-white"
|
|
}`}
|
|
>
|
|
全部
|
|
</Link>
|
|
{ASR_CONTENT_TAGS.map((tag) => (
|
|
<Link
|
|
key={tag}
|
|
href={`/?tag=${encodeURIComponent(tag)}`}
|
|
className={`rounded-full border px-3 py-1.5 text-sm font-medium transition-colors ${
|
|
selectedTag === tag
|
|
? "border-blue-500 bg-blue-600 text-white"
|
|
: "border-zinc-200 bg-white/70 text-zinc-600 hover:border-zinc-300 dark:border-white/10 dark:bg-white/5 dark:text-zinc-300 dark:hover:text-white"
|
|
}`}
|
|
>
|
|
{tag}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
|
|
<div className="w-full">
|
|
{(() => {
|
|
const initial = feed.slice(0, 24);
|
|
const lastItem = initial.at(-1);
|
|
const cursor = lastItem
|
|
? new Date(lastItem.created_at).toISOString()
|
|
: null;
|
|
return (
|
|
<FeedMasonry
|
|
key={selectedTag ?? "all"}
|
|
initialItems={initial}
|
|
initialCursor={cursor}
|
|
fetchUrl={
|
|
selectedTag
|
|
? `/api/feed?tag=${encodeURIComponent(selectedTag)}`
|
|
: "/api/feed"
|
|
}
|
|
/>
|
|
);
|
|
})()}
|
|
</div>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|