Skip to content

Commit

Permalink
Merge pull request #27 from immccn123/snapshot-tl
Browse files Browse the repository at this point in the history
feat: 用户名历史时间线
  • Loading branch information
wxh06 authored Aug 11, 2024
2 parents 2be8261 + a617ef8 commit 2b91bb3
Show file tree
Hide file tree
Showing 13 changed files with 353 additions and 12 deletions.
1 change: 1 addition & 0 deletions packages/archive/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"main": "dist/server.js",
"scripts": {
"build": "tsc",
"start": "node dist/server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/archive/src/lib/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { BaseLogger } from "pino";
import type { PrismaClient, PrismaPromise } from "@prisma/client";
import { getResponse } from "./parser";
import type { UserSummary } from "./user";
import { upsertUserSnapshotHook } from "./user";
import { upsertUserSnapshot } from "./user";

export interface Activity {
content: string;
Expand Down Expand Up @@ -37,7 +37,7 @@ export async function saveActivityPage(
// eslint-disable-next-line no-restricted-syntax
for (const { user } of res.feeds.result) {
// eslint-disable-next-line no-await-in-loop
await upsertUserSnapshotHook(prisma, user);
await upsertUserSnapshot(prisma, user);
}

res.feeds.result.forEach((activity) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/archive/src/lib/judgement.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { PrismaClient, PrismaPromise } from "@prisma/client";
import type { BaseLogger } from "pino";
import { getResponse } from "./parser";
import { UserSummary, upsertUserSnapshotHook } from "./user";
import { UserSummary, upsertUserSnapshot } from "./user";

interface JudgementBody {
user: UserSummary;
Expand Down Expand Up @@ -43,7 +43,7 @@ export default async function saveJudgements(
// eslint-disable-next-line no-restricted-syntax
for (const judgement of judgements) {
// eslint-disable-next-line no-await-in-loop
await upsertUserSnapshotHook(prisma, judgement.user);
await upsertUserSnapshot(prisma, judgement.user);
if (new Date(judgement.time * 1000) <= latestJudgement.time) break;
operations.push(
prisma.judgement.upsert({
Expand Down
6 changes: 5 additions & 1 deletion packages/archive/src/lib/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ export default async function getPostList(
await prisma.post.findMany({
select: {
id: true,
replies: { select: { id: true }, orderBy: { id: "desc" }, take: 1 },
replies: {
select: { id: true },
orderBy: { id: "desc" },
take: 1,
},
},
where: {
id: {
Expand Down
4 changes: 2 additions & 2 deletions packages/archive/src/lib/paste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { BaseLogger } from "pino";
import type { PrismaClient } from "@prisma/client";
import { getResponse } from "./parser";
import { type UserSummary } from "./user";
import { upsertUserSnapshotHook } from "./user";
import { upsertUserSnapshot } from "./user";

interface Paste {
data: string;
Expand Down Expand Up @@ -49,7 +49,7 @@ export default async function savePaste(
}
if (json.code !== 200) throw Error(json.currentData.errorMessage);
const { paste } = json.currentData;
await upsertUserSnapshotHook(prisma, paste.user);
await upsertUserSnapshot(prisma, paste.user);
await prisma.$transaction([
prisma.paste.upsert({
where: { id: paste.id },
Expand Down
6 changes: 3 additions & 3 deletions packages/archive/src/lib/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { BroadcastOperator } from "socket.io";
import type { PostSnapshot, PrismaClient } from "@prisma/client";
import { getResponse } from "./parser";
import type { ServerToClientEvents } from "../plugins/socket.io";
import { UserSummary, upsertUserSnapshotHook } from "./user";
import { UserSummary, upsertUserSnapshot } from "./user";

const PAGES_PER_SAVE = parseInt(process.env.PAGES_PER_SAVE ?? "64", 10);
export const emitters: Record<number, EventEmitter> = {};
Expand Down Expand Up @@ -73,7 +73,7 @@ export async function savePost(
// eslint-disable-next-line no-restricted-syntax
for (const { author } of replies) {
// eslint-disable-next-line no-await-in-loop
await upsertUserSnapshotHook(prisma, author);
await upsertUserSnapshot(prisma, author);
}
allReplies = [...allReplies, ...replies];
};
Expand Down Expand Up @@ -129,7 +129,7 @@ export async function savePost(
const { post, replies, forum } = (await fetchPage(1)).currentData;
const postTime = new Date(post.time * 1000);

await upsertUserSnapshotHook(prisma, post.author);
await upsertUserSnapshot(prisma, post.author);

await prisma.$transaction(
async (tx) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/archive/src/lib/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface UserSummary {
isRoot?: true;
}

export const upsertUserSnapshotHook = async (
export const upsertUserSnapshot = async (
prisma: PrismaClient,
user: UserSummary,
) => {
Expand Down
1 change: 1 addition & 0 deletions packages/viewer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"rehype-katex": "^7.0.0",
"remark-luogu-flavor": "^1.0.0",
"remark-math": "^6.0.0",
"rsuite": "^5.68.1",
"socket.io-client": "^4.7.5",
"swr": "^2.2.5"
},
Expand Down
97 changes: 97 additions & 0 deletions packages/viewer/src/app/user/[uid]/SnapshotTimeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"use client";

import Spinner from "@/components/Spinner";
import UserInfo from "@/components/UserInfo";
import fetcher from "@/lib/fetcher";
import { UserSnapshot } from "@prisma/client";
import Timeline from "rsuite/Timeline";
import useSWRInfinite from "swr/infinite";
import "rsuite/Timeline/styles/index.css";
import { useState } from "react";
import { BsChevronDown, BsChevronUp, BsThreeDots } from "react-icons/bs";

const PER_PAGE = 15;

interface PageData {
snapshots: UserSnapshot[];
nextCursor: string;
}

export default function SnapshotTimeline({ uid }: { uid: number }) {
const [open, setOpen] = useState(false);

const { data, size, setSize, isValidating } = useSWRInfinite<PageData>(
(_pageIndex, prev: PageData | null) => {
if (prev && !prev.nextCursor) return null;
let res = `/user/${uid}/snapshots?limit=${PER_PAGE}`;
if (prev) res += `&offset=${prev.nextCursor}`;
return res;
},
fetcher,
);

const timeline = (
<>
<Timeline endless isItemActive={Timeline.ACTIVE_FIRST}>
{data?.map((dat) =>
dat.snapshots?.map((snapshot) => (
<Timeline.Item>
<UserInfo
user={{
id: uid,
userSnapshots: [snapshot],
}}
noHref
/>
<br />
截至 {new Date(snapshot.until).toLocaleString()}
</Timeline.Item>
)),
)}
</Timeline>
{isValidating && <Spinner className="mt-5" />}
{!isValidating &&
data &&
data[data.length - 1].snapshots.length === PER_PAGE && (
<button
className="btn btn-link w-100 rounded-4 py-2x py-md-3 text-center text-decoration-none"
onClick={() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
setSize(size + 1);
}}
type="button"
>
加载更多
<BsThreeDots
className="ms-1"
style={{ position: "relative", top: "-.09em" }}
/>
</button>
)}
</>
);

return (
<>
<div className="d-md-none">
<button
type="button"
className="btn btn-link w-100 d-md-none"
onMouseDown={() => setOpen(!open)}
style={{ textDecoration: "none" }}
>
用户名历史 {open ? <BsChevronUp /> : <BsChevronDown />}
</button>

{open && (
<>
{/* 对于缺失空白的一个并不优雅的解决方案 */}
<div style={{ height: "10px" }} className="d-md-none" />
{timeline}
</>
)}
</div>
<div className="d-md-block d-none">{timeline}</div>
</>
);
}
5 changes: 5 additions & 0 deletions packages/viewer/src/app/user/[uid]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "@/components/markdown.css";
import { selectUser } from "@/lib/user";
import TabNavigation from "./TabNavigation";
import UserStatistics from "./UserStatistics";
import SnapshotTimeline from "./SnapshotTimeline";

export async function generateMetadata({
params,
Expand Down Expand Up @@ -65,6 +66,10 @@ export default async function Layout({
</a>
</div>
</div>

<div className="rounded-4 shadow-bssb px-4 py-4 text-center mt-3">
<SnapshotTimeline uid={user.id} />
</div>
</div>
<div className="col-md-8 col-12">
<TabNavigation uid={params.uid} />
Expand Down
26 changes: 26 additions & 0 deletions packages/viewer/src/app/user/[uid]/snapshots/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import prisma from "@/lib/prisma";
import { NextRequest, NextResponse } from "next/server";

export async function GET(
request: NextRequest,
{ params }: { params: { uid: string } },
) {
const userId = +params.uid;
const limit = request.nextUrl.searchParams.get("limit") ?? 10;
const offset = request.nextUrl.searchParams.get("offset");

// 按照时间降序排序 跳过 offset 个
const snapshots = await prisma.userSnapshot.findMany({
where: {
userId,
time: { lt: offset ? new Date(offset) : undefined },
},
orderBy: { time: "desc" },
take: limit ? +limit : undefined,
});

return NextResponse.json({
snapshots,
nextCursor: snapshots[snapshots.length - 1]?.time ?? null,
});
}
14 changes: 13 additions & 1 deletion packages/viewer/src/components/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,29 @@ export default function UserInfo({
user,
snapshotId,
href,
noHref,
}: {
user: LatestUser;
// eslint-disable-next-line react/require-default-props
snapshotId?: number;
// eslint-disable-next-line react/require-default-props
href?: string;
// eslint-disable-next-line react/require-default-props
noHref?: boolean;
}) {
const snapshot = user.userSnapshots[snapshotId ?? 0];
return (
<span className="text-nowrap">
{href === undefined ? (
{/* eslint-disable-next-line no-nested-ternary */}
{noHref ? (
<span
className={`text-decoration-none lg-fg-${getNameClassByColor(
snapshot.color,
)}`}
>
{snapshot.name}
</span>
) : href === undefined ? (
<Link
href={getUserUrl(user.id)}
className={`text-decoration-none lg-fg-${getNameClassByColor(
Expand Down
Loading

0 comments on commit 2b91bb3

Please sign in to comment.