Skip to content

Commit

Permalink
回复取下与渲染优化
Browse files Browse the repository at this point in the history
  • Loading branch information
bohanjun committed Jul 20, 2023
1 parent 78d8cf7 commit 8999a22
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 8 deletions.
18 changes: 17 additions & 1 deletion packages/viewer/src/app/[id]/[page]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,23 @@ export default async function Page({
take: 1,
},
replies: {
select: { id: true, author: true, time: true, content: true },
select: {
id: true,
author: true,
time: true,
content: true,
takedown: {
select: {
submitter: {
select: {
id: true,
username: true,
},
},
reason: true,
},
},
},
orderBy: { id: "asc" },
skip: (page - 1) * REPLIES_PER_PAGE,
take: REPLIES_PER_PAGE,
Expand Down
36 changes: 36 additions & 0 deletions packages/viewer/src/app/[id]/context/[user]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,47 @@ export async function GET(
/* eslint-enable @typescript-eslint/no-non-null-assertion */
const reply = await (offset <= 0
? prisma.reply.findFirst({
select: {
id: true,
author: true,
time: true,
content: true,
discussionId: true,
takedown: {
select: {
submitter: {
select: {
id: true,
username: true,
},
},
reason: true,
},
},
},
where: { discussionId, authorId, id: { lt: replyId } },
orderBy: { id: "desc" },
skip: -offset,
})
: prisma.reply.findFirst({
select: {
id: true,
author: true,
time: true,
content: true,
discussionId: true,
takedown: {
select: {
submitter: {
select: {
id: true,
username: true,
},
},
reason: true,
},
},
},
where: { discussionId, authorId, id: { gt: replyId } },
orderBy: { id: "asc" },
skip: offset - 1,
Expand Down
19 changes: 18 additions & 1 deletion packages/viewer/src/app/[id]/replies/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,28 @@ export async function GET(
const cursor = request.nextUrl.searchParams.get("cursor");
const limit = request.nextUrl.searchParams.get("limit");
const replies = await prisma.reply.findMany({
select: {
id: true,
author: true,
time: true,
content: true,
discussionId: true,
takedown: {
select: {
submitter: {
select: {
id: true,
username: true,
},
},
reason: true,
},
},
},
where: {
discussionId: id,
id: { gt: cursor ? parseInt(cursor, 10) : undefined },
},
select: { id: true, time: true, author: true, content: true },
take: parseInt(limit ?? "10", 10),
});
return NextResponse.json({
Expand Down
12 changes: 12 additions & 0 deletions packages/viewer/src/app/bootstrap.scss
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,15 @@ details[open] summary .open-show-inline {
bottom: -2.8em;
}
}

.link-at-user {
text-decoration: none;
}

.link-at-user:hover {
text-decoration: underline;
}

.link-at-user-unstored {
color: #00b5ad;
}
11 changes: 11 additions & 0 deletions packages/viewer/src/app/r/[rid]/get-reply-raw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ export default async (id: number) =>
},
},
},
takedown: {
select: {
submitter: {
select: {
id: true,
username: true,
},
},
reason: true,
},
},
},
where: { id },
})) ?? notFound();
12 changes: 12 additions & 0 deletions packages/viewer/src/lib/luogu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ export function getUserIdFromUrl(target: URL) {
return Number.isNaN(uid) ? null : uid;
}

export function getDiscussionIdFromUrl(target: URL) {
if (!isLuoguUrl(target)) return null;
const discussionId = parseInt(
(target.pathname.startsWith("/discuss/") &&
target.pathname.split("/")[2]) ||
((target.pathname === "/discuss/show" &&
target.searchParams.get("postid")) as string),
10,
);
return Number.isNaN(discussionId) ? null : discussionId;
}

export function getDiscussionId(s: string) {
const id = parseInt(s, 10);
if (!Number.isNaN(id)) return id;
Expand Down
107 changes: 101 additions & 6 deletions packages/viewer/src/lib/serialize-reply.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { JSDOM } from "jsdom";
import hljs from "highlight.js";
import type { User } from "@prisma/client";
import type { Discussion, ReplyTakedown, User } from "@prisma/client";
import prisma from "@/lib/prisma";
import { getUserIdFromUrl, getUserUrl } from "@/lib/luogu";
import {
getDiscussionIdFromUrl,
getUserIdFromUrl,
getUserUrl,
getUserRealUrl,
} from "@/lib/luogu";
import stringifyTime from "@/lib/time";

export type UserMetioned = User & { numReplies?: number };
Expand All @@ -16,6 +21,26 @@ function getMentionedUser(element: Element) {
);
}

function getDiscussionUrl(discussionId: number) {
return `/${discussionId}`;
}

function getHtmlTookdown(takedown: {
submitter: { id: number; username: string };
reason: string;
}) {
return `<p class="text-danger">该回复已按
<span class="text-body-tertiary">
@<a href="${getUserRealUrl(takedown.submitter.id)}">${
takedown.submitter.username
}</a>
</span> 要求删除。
</p>
<blockquote>
<p>${takedown.reason}</p>
</blockquote>`;
}

hljs.registerAliases(["plain"], { languageName: "plaintext" });
hljs.configure({ languages: ["cpp"] });

Expand All @@ -27,11 +52,26 @@ function renderHljs(body: HTMLElement) {

export default async function serializeReply(
discussionId: number,
{ content, time }: { content: string; time: Date },
{
content,
time,
takedown,
}: {
content: string;
time: Date;
takedown?: {
submitter: { id: number; username: string };
reason: string;
} | null;
},
) {
const users: number[] = [];
const userElements: { ele: Element; user: number }[] = [];
const discussions: number[] = [];
const discussionElements: { ele: Element; discussion: number }[] = [];

const { document } = new JSDOM(content).window;
const { document } = new JSDOM(takedown ? getHtmlTookdown(takedown) : content)
.window;
document.body.querySelectorAll("a[href]").forEach((element) => {
element.setAttribute("target", "_blank");
element.setAttribute("rel", "noopener noreferrer");
Expand All @@ -44,9 +84,26 @@ export default async function serializeReply(
const uid = getMentionedUser(element);
if (uid) {
users.push(uid);
userElements.push({ ele: element, user: uid });
element.setAttribute("data-uid", uid.toString());
element.classList.add("text-decoration-none", "link-teal");
// element.classList.add("text-decoration-none", "link-teal");
element.setAttribute("href", getUserUrl(uid));
} else {
const mentionedDiscussionId = getDiscussionIdFromUrl(
new URL(element.getAttribute("href") as string),
);
if (mentionedDiscussionId) {
discussions.push(mentionedDiscussionId);
discussionElements.push({
ele: element,
discussion: mentionedDiscussionId,
});
element.setAttribute(
"data-discussion-id",
mentionedDiscussionId.toString(),
);
element.setAttribute("href", getDiscussionUrl(mentionedDiscussionId));
}
}
} catch (e) {
// Invalid URL
Expand All @@ -62,6 +119,38 @@ export default async function serializeReply(
.then((r) => Object.fromEntries(r.map((u) => [u.authorId, u._count]))),
prisma.user.findMany({ where: { id: { in: users } } }),
]);
const indUsersMetioned: { [k: number]: User } = {};
// eslint-disable-next-line no-return-assign
usersMetioned.forEach((el) => (indUsersMetioned[el.id] = el));
// eslint-disable-next-line no-restricted-syntax
for (const ue of userElements) {
if (ue.user in indUsersMetioned) {
ue.ele.classList.add(
`lg-fg-${indUsersMetioned[ue.user].color}`,
"link-at-user",
);
} else {
ue.ele.classList.add("link-at-user", "link-at-user-unstored");
}
}
const discussionsMetioned = await prisma.discussion.findMany({
where: { id: { in: discussions } },
});
const indDiscussionsMetioned: { [k: number]: Discussion } = {};
// eslint-disable-next-line no-return-assign
discussionsMetioned.forEach((el) => (indDiscussionsMetioned[el.id] = el));
// eslint-disable-next-line no-restricted-syntax
for (const de of discussionElements) {
if (de.discussion in indDiscussionsMetioned) {
de.ele.classList.add("link-teal", "link-discussion");
} else {
de.ele.classList.add(
"link-danger",
"link-discussion",
"link-discussion-unstored",
);
}
}
renderHljs(document.body);
return {
content: document.body.innerHTML,
Expand All @@ -76,16 +165,22 @@ export default async function serializeReply(
export async function serializeReplyNoninteractive({
content,
time,
takedown,
}: {
content: string;
time: Date;
takedown?: {
submitter: { id: number; username: string };
reason: string;
} | null;
}) {
const users: number[] = [];
const userElements: { ele: Element; user: number }[] = [];
const links: Set<string> = new Set();
const linkElements: { ele: Element; link: string }[] = [];

const { document } = new JSDOM(content).window;
const { document } = new JSDOM(takedown ? getHtmlTookdown(takedown) : content)
.window;
document.body.querySelectorAll("a[href]").forEach((element) => {
element.setAttribute("target", "_blank");
element.setAttribute("rel", "noopener noreferrer");
Expand Down

0 comments on commit 8999a22

Please sign in to comment.