diff --git a/notice.md b/notice.md deleted file mode 100644 index e69de29..0000000 diff --git a/packages/viewer/package.json b/packages/viewer/package.json index 7fe1af1..b3d1816 100644 --- a/packages/viewer/package.json +++ b/packages/viewer/package.json @@ -20,6 +20,9 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-infinite-scroll-component": "^6.1.0", + "react-markdown": "^9.0.0", + "rehype-katex": "^7.0.0", + "remark-math": "^6.0.0", "socket.io-client": "^4.7.2", "swr": "^2.2.2" }, diff --git a/packages/viewer/src/app/[id]/[page]/page.tsx b/packages/viewer/src/app/[id]/[page]/page.tsx index fb4e778..513b534 100644 --- a/packages/viewer/src/app/[id]/[page]/page.tsx +++ b/packages/viewer/src/app/[id]/[page]/page.tsx @@ -2,8 +2,10 @@ import { notFound } from "next/navigation"; import prisma from "@/lib/prisma"; import paginate from "@/lib/pagination"; import PageButtons from "@/components/replies/PageButtons"; -import serializeReply from "@/lib/serialize-reply"; +// import serializeReply from "@/lib/serialize-reply"; import Reply from "@/components/replies/Reply"; +import { selectReply } from "@/lib/reply"; +import { selectPost } from "@/lib/post"; const REPLIES_PER_PAGE = parseInt(process.env.REPLIES_PER_PAGE ?? "10", 10); @@ -16,35 +18,19 @@ export default async function Page({ const page = parseInt(params.page, 10); if (Number.isNaN(page)) notFound(); const { - snapshots: [{ authorId }], + snapshots, replies, _count: { replies: numReplies }, } = await prisma.post .findUnique({ where: { id }, select: { - snapshots: { - select: { authorId: true }, - orderBy: { time: "desc" }, - take: 1, - }, + ...selectPost.withLatestSnapshotMeta, replies: { select: { - id: true, - author: true, - time: true, - content: true, - takedown: { - select: { - submitter: { - select: { - id: true, - username: true, - }, - }, - reason: true, - }, - }, + ...selectReply.withBasic, + ...selectReply.withTakedown, + ...selectReply.withLatestContent, }, orderBy: { id: "asc" }, skip: (page - 1) * REPLIES_PER_PAGE, @@ -53,16 +39,7 @@ export default async function Page({ _count: { select: { replies: true } }, }, }) - .then((discussion) => discussion ?? notFound()) - .then(async (discussion) => ({ - ...discussion, - replies: await Promise.all( - discussion.replies.map(async (reply) => ({ - ...reply, - ...(await serializeReply(id, reply)), - })), - ), - })); + .then((post) => post ?? notFound()) const numPages = Math.ceil(numReplies / REPLIES_PER_PAGE); const { pagesLocalAttachedFront, pagesLocalAttachedBack, pagesLocal } = paginate(numPages, page); @@ -70,7 +47,7 @@ export default async function Page({ return ( <> {replies.map((reply) => ( - + ))} {numPages > 1 && (
diff --git a/packages/viewer/src/app/[id]/context/[user]/route.ts b/packages/viewer/src/app/[id]/context/[user]/route.ts index afed980..be508c9 100644 --- a/packages/viewer/src/app/[id]/context/[user]/route.ts +++ b/packages/viewer/src/app/[id]/context/[user]/route.ts @@ -1,12 +1,13 @@ import { NextRequest, NextResponse } from "next/server"; +import { selectReply } from "@/lib/reply"; import prisma from "@/lib/prisma"; -import serializeReply from "@/lib/serialize-reply"; +// import serializeReply from "@/lib/serialize-reply"; export async function GET( request: NextRequest, - { params }: { params: { id: string; user: string } }, + { params }: { params: { id: string; user: string } } ) { - const discussionId = parseInt(params.id, 10); + const postId = parseInt(params.id, 10); const authorId = parseInt(params.user, 10); /* eslint-disable @typescript-eslint/no-non-null-assertion */ const replyId = parseInt(request.nextUrl.searchParams.get("reply")!, 10); @@ -15,54 +16,36 @@ export async function GET( 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, - }, + ...selectReply.withBasic, + ...selectReply.withTakedown, + ...selectReply.withLatestContent, + }, + where: { + postId, + snapshots: { + some: { authorId }, }, + id: { lt: replyId }, }, - 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, - }, + ...selectReply.withBasic, + ...selectReply.withTakedown, + ...selectReply.withLatestContent, + }, + where: { + postId, + snapshots: { + some: { authorId }, }, + id: { gt: replyId }, }, - where: { discussionId, authorId, id: { gt: replyId } }, orderBy: { id: "asc" }, skip: offset - 1, })); - if (reply) - return NextResponse.json({ - ...reply, - ...(await serializeReply(discussionId, reply)), - }); + if (reply) return NextResponse.json({ reply }); return NextResponse.json({}); } diff --git a/packages/viewer/src/app/[id]/layout.tsx b/packages/viewer/src/app/[id]/layout.tsx index 21e2610..5013c79 100644 --- a/packages/viewer/src/app/[id]/layout.tsx +++ b/packages/viewer/src/app/[id]/layout.tsx @@ -1,13 +1,13 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; import prisma from "@/lib/prisma"; -import { getDiscussionUrl, getForumName, getForumUrl } from "@/lib/luogu"; +import { getPostUrl, getForumUrl } from "@/lib/luogu"; import stringifyTime from "@/lib/time"; import UserInfo from "@/components/UserInfo"; import "@/components/markdown.css"; import UpdateButton from "@/components/UpdateButton"; -import serializeReply from "@/lib/serialize-reply"; import Reply from "@/components/replies/Reply"; +import { selectPost } from "@/lib/post"; export async function generateMetadata({ params, @@ -16,16 +16,14 @@ export async function generateMetadata({ }): Promise { const id = parseInt(params.id, 10); if (Number.isNaN(id)) notFound(); - const snapshot = await prisma.snapshot.findFirst({ - select: { title: true, discussion: { select: { takedown: true } } }, + const snapshot = await prisma.postSnapshot.findFirst({ + select: { title: true, post: { select: { takedown: true } } }, orderBy: { time: "desc" }, - where: { discussionId: id }, + where: { postId: id }, }); return { title: `${ - snapshot && !snapshot.discussion.takedown - ? `「${snapshot.title}」` - : "404" + snapshot && !snapshot.post.takedown ? `「${snapshot.title}」` : "404" } - 洛谷帖子保存站`, }; } @@ -42,23 +40,12 @@ export default async function Page({ snapshots: [{ title, forum, author, content, until: updatedAt }], _count: { replies }, takedown, - } = (await prisma.discussion.findUnique({ + } = (await prisma.post.findUnique({ where: { id }, select: { - time: true, - replyCount: true, - snapshots: { - select: { - until: true, - title: true, - forum: true, - author: true, - content: true, - }, - orderBy: { time: "desc" }, - take: 1, - }, - takedown: { select: { reason: true, submitter: true } }, + ...selectPost.withBasic, + ...selectPost.withLatestContent, + ...selectPost.withTakedown, _count: { select: { replies: true } }, }, })) ?? notFound(); @@ -87,7 +74,7 @@ export default async function Page({ target="_blank" rel="noopener noreferrer" > - {getForumName(forum)} + {forum.name}
  • @@ -118,7 +105,7 @@ export default async function Page({ {children} diff --git a/packages/viewer/src/app/[id]/replies/route.ts b/packages/viewer/src/app/[id]/replies/route.ts index 37672bf..8202a6b 100644 --- a/packages/viewer/src/app/[id]/replies/route.ts +++ b/packages/viewer/src/app/[id]/replies/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import prisma from "@/lib/prisma"; -import serializeReply from "@/lib/serialize-reply"; +// import serializeReply from "@/lib/serialize-reply"; import { selectReply } from "@/lib/reply"; @@ -24,12 +24,7 @@ export async function GET( take: parseInt(limit ?? "10", 10), }); return NextResponse.json({ - data: await Promise.all( - replies.map(async (reply) => ({ - ...reply, - ...(await serializeReply(id, reply)), - })), - ), + replies, nextCursor: replies.length ? replies[replies.length - 1].id : null, }); } diff --git a/packages/viewer/src/app/r/[rid]/page.tsx b/packages/viewer/src/app/r/[rid]/page.tsx index e473c86..06c91ef 100644 --- a/packages/viewer/src/app/r/[rid]/page.tsx +++ b/packages/viewer/src/app/r/[rid]/page.tsx @@ -1,3 +1,5 @@ +// eslint-disable +// Not impl yet import { notFound } from "next/navigation"; import Link from "next/link"; import prisma from "@/lib/prisma"; @@ -13,6 +15,8 @@ export const metadata = { title: "金玉良言 - 洛谷帖子保存站" }; const REPLIES_PER_PAGE = parseInt(process.env.REPLIES_PER_PAGE ?? "10", 10); export default async function Page({ params }: { params: { rid: string } }) { + return <>抱歉,本功能暂未完成 TAT; + const id = parseInt(params.rid, 10); if (Number.isNaN(id)) notFound(); const replyRaw = await getReplyRaw(id); diff --git a/packages/viewer/src/app/user/[uid]/page.tsx b/packages/viewer/src/app/user/[uid]/page.tsx index 99467db..b5ba744 100644 --- a/packages/viewer/src/app/user/[uid]/page.tsx +++ b/packages/viewer/src/app/user/[uid]/page.tsx @@ -1,11 +1,13 @@ import { notFound } from "next/navigation"; import prisma from "@/lib/prisma"; import UserParticipated from "./participated/UserParticipated"; +import { selectUser } from "@/lib/user"; export default async function Page({ params }: { params: { uid: string } }) { const user = (await prisma.user.findUnique({ where: { id: parseInt(params.uid, 10) }, + select: selectUser.withLatest, })) ?? notFound(); return ; } diff --git a/packages/viewer/src/app/user/[uid]/participated/UserParticipated.tsx b/packages/viewer/src/app/user/[uid]/participated/UserParticipated.tsx index 391521c..43c4eae 100644 --- a/packages/viewer/src/app/user/[uid]/participated/UserParticipated.tsx +++ b/packages/viewer/src/app/user/[uid]/participated/UserParticipated.tsx @@ -3,7 +3,6 @@ import Link from "next/link"; import useSWRInfinite from "swr/infinite"; import InfiniteScroll from "react-infinite-scroll-component"; -import type { User } from "@prisma/client"; import type { PostWithLatestContent } from "@/lib/post"; import type { UserMetioned } from "@/lib/serialize-reply"; import UserAvatar from "@/components/UserAvatar"; @@ -12,6 +11,7 @@ import Content from "@/components/replies/Content"; import fetcher from "@/lib/fetcher"; import Spinner from "@/components/Spinner"; import { NUM_MAX_REPLIES_SHOWED_DEFAULT } from "../constants"; +import { LatestUser } from "@/lib/user"; interface PageData { data: (PostWithLatestContent & { @@ -36,7 +36,7 @@ export default function UserParticipated({ user, }: { uid: string; - user: User; + user: LatestUser; }) { const { data, size, setSize, isValidating } = useSWRInfinite( (pageIndex: number, previousPageData: PageData) => @@ -137,7 +137,7 @@ export default function UserParticipated({ ) : undefined} @@ -235,7 +235,7 @@ export default function UserParticipated({ discussion.snapshots[0].author.id } content={reply.content} - usersMetioned={reply.usersMetioned} + // usersMetioned={reply.usersMetioned} /> {href === undefined ? ( - {user.username} + {snapshot.name} ) : ( - {user.username} + {snapshot.name} )} - {user.checkmark && ( + {snapshot.ccfLevel >= 3 && ( )} - {user.badge && ( + {snapshot.badge && ( - {user.badge} + {snapshot.badge} )} diff --git a/packages/viewer/src/components/replies/Content.tsx b/packages/viewer/src/components/replies/Content.tsx index 43edd6c..d236ee9 100644 --- a/packages/viewer/src/components/replies/Content.tsx +++ b/packages/viewer/src/components/replies/Content.tsx @@ -1,94 +1,96 @@ +// eslint-disable +// Not impl: Context "use client"; -import "katex/dist/katex.css"; +import "katex/dist/katex.min.css"; import "highlight.js/styles/tokyo-night-dark.css"; import { useEffect, useRef } from "react"; -import renderMathInElement from "katex/contrib/auto-render"; -import { computePosition, shift } from "@floating-ui/dom"; -import type { UserMetioned } from "@/lib/serialize-reply"; -import UserInfo from "@/components/UserInfo"; -import UserAvatar from "@/components/UserAvatar"; +// import { computePosition, shift } from "@floating-ui/dom"; +// import type { UserMetioned } from "@/lib/serialize-reply"; +// import UserInfo from "@/components/UserInfo"; +// import UserAvatar from "@/components/UserAvatar"; +import Markdown from "react-markdown"; +import rehypeKatex from "rehype-katex"; +import remarkMath from "remark-math"; export default function Content({ + // Markdown content, discussionAuthor, - usersMetioned, + // usersMetioned, userIdState, }: { content: string; discussionAuthor: number; - usersMetioned: UserMetioned[]; + // usersMetioned: UserMetioned[]; // eslint-disable-next-line react/require-default-props userIdState?: [number | null, (userId: number | null) => void]; }) { const contentRef = useRef(null); - const userRefs = useRef>({}); + // const userRefs = useRef>({}); - useEffect(() => { - renderMathInElement(contentRef.current!, { - delimiters: [ - { left: "$$", right: "$$", display: true }, - { left: "$", right: "$", display: false }, - ], - }); + // useEffect(() => { + // contentRef.current?.querySelectorAll("a[data-uid]").forEach((element) => { + // const uid = parseInt(element.getAttribute("data-uid")!, 10); + // const tooltip = userRefs.current[uid]!; + // if (!tooltip) return; - contentRef.current?.querySelectorAll("a[data-uid]").forEach((element) => { - const uid = parseInt(element.getAttribute("data-uid")!, 10); - const tooltip = userRefs.current[uid]!; - if (!tooltip) return; + // function update() { + // // eslint-disable-next-line @typescript-eslint/no-floating-promises + // computePosition(element as HTMLElement, tooltip, { + // placement: "top", + // middleware: [shift()], + // }).then(({ x, y }) => + // Object.assign(tooltip.style, { left: `${x}px`, top: `${y}px` }), + // ); + // } + // function showTooltip() { + // update(); + // tooltip.style.display = "block"; + // } + // function hideTooltip() { + // tooltip.style.display = "none"; + // } - function update() { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - computePosition(element as HTMLElement, tooltip, { - placement: "top", - middleware: [shift()], - }).then(({ x, y }) => - Object.assign(tooltip.style, { left: `${x}px`, top: `${y}px` }), - ); - } - function showTooltip() { - update(); - tooltip.style.display = "block"; - } - function hideTooltip() { - tooltip.style.display = "none"; - } - - ( - [ - ["mouseenter", showTooltip], - ["mouseleave", hideTooltip], - ["focus", showTooltip], - ["blur", hideTooltip], - ...(userIdState - ? [ - [ - "click", - (event: Event) => { - event.preventDefault(); - hideTooltip(); - if (userIdState[0] !== uid) userIdState[1](uid); - else userIdState[1](null); - }, - ], - ] - : []), - ] as [string, () => void][] - ).forEach(([event, listener]) => - element.addEventListener(event, listener), - ); - }); - }); + // ( + // [ + // ["mouseenter", showTooltip], + // ["mouseleave", hideTooltip], + // ["focus", showTooltip], + // ["blur", hideTooltip], + // ...(userIdState + // ? [ + // [ + // "click", + // (event: Event) => { + // event.preventDefault(); + // hideTooltip(); + // if (userIdState[0] !== uid) userIdState[1](uid); + // else userIdState[1](null); + // }, + // ], + // ] + // : []), + // ] as [string, () => void][] + // ).forEach(([event, listener]) => + // element.addEventListener(event, listener), + // ); + // }); + // }); return ( <>
    - {usersMetioned.map((user) => ( + + {/* {usersMetioned.map((user) => (
    { userRefs.current[user.id] = el; @@ -134,7 +136,7 @@ export default function Content({
  • - ))} + ))} */} ); } diff --git a/packages/viewer/src/components/replies/ContextViewer.tsx b/packages/viewer/src/components/replies/ContextViewer.tsx index 6d9eecb..30e5d61 100644 --- a/packages/viewer/src/components/replies/ContextViewer.tsx +++ b/packages/viewer/src/components/replies/ContextViewer.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import useSWR from "swr"; -import type { User } from "@prisma/client"; import fetcher from "@/lib/fetcher"; import UserInfo from "@/components/UserInfo"; import type { UserMetioned } from "@/lib/serialize-reply"; @@ -22,122 +21,126 @@ export default function ContextViewer({ discussionId, userId, replyId, - userMetioned, + // userMetioned, }: { discussionAuthor: number; discussionId: number; userId: number; replyId: number; - userMetioned: User; + // userMetioned: User; }) { - const [pageIndex, setPageIndex] = useState(0); - const { data, isLoading } = useSWR( - `/${discussionId}/context/${userId}?reply=${replyId}&offset=${pageIndex}`, - fetcher, - ); - return ( -
    -
    -
    - {/* eslint-disable-next-line no-nested-ternary */} - {isLoading ? ( -
    -
    加载中
    -
    - ) : Object.keys(data ?? {}).length ? ( -
    -
    - -
    - {/* eslint-disable-next-line no-nested-ternary */} - {pageIndex === 0 ? ( - - 推测的上文,发布于 {data!.time} - - ) : pageIndex < 0 ? ( - - 可能的上文,发布于 {data!.time} - - ) : ( - - 本层后发布,发布于 {data!.time} - - )} - -
    - ) : ( -
    -
    - 空空如也,真好奇 {" "} - 到底说过些什么呢? -
    -
    - )} -
    -
    - - -
    -
    -
    - ); + + // TODO: TBD here + return <>; + + // const [pageIndex, setPageIndex] = useState(0); + // const { data, isLoading } = useSWR( + // `/${discussionId}/context/${userId}?reply=${replyId}&offset=${pageIndex}`, + // fetcher, + // ); + // return ( + //
    + //
    + //
    + // {/* eslint-disable-next-line no-nested-ternary */} + // {isLoading ? ( + //
    + //
    加载中
    + //
    + // ) : Object.keys(data ?? {}).length ? ( + //
    + //
    + // + //
    + // {/* eslint-disable-next-line no-nested-ternary */} + // {pageIndex === 0 ? ( + // + // 推测的上文,发布于 {data!.time} + // + // ) : pageIndex < 0 ? ( + // + // 可能的上文,发布于 {data!.time} + // + // ) : ( + // + // 本层后发布,发布于 {data!.time} + // + // )} + // + //
    + // ) : ( + //
    + //
    + // 空空如也,真好奇 {" "} + // 到底说过些什么呢? + //
    + //
    + // )} + //
    + //
    + // + // + //
    + //
    + //
    + // ); } diff --git a/packages/viewer/src/components/replies/InfiniteScrollReplies.tsx b/packages/viewer/src/components/replies/InfiniteScrollReplies.tsx index e2ad0fa..f861b64 100644 --- a/packages/viewer/src/components/replies/InfiniteScrollReplies.tsx +++ b/packages/viewer/src/components/replies/InfiniteScrollReplies.tsx @@ -9,15 +9,10 @@ import fetcher from "@/lib/fetcher"; import Spinner from "@/components/Spinner"; import PageButtons from "./PageButtons"; import Reply from "./Reply"; +import { ReplyWithLatestContent } from "@/lib/reply"; interface PageData { - data: { - id: number; - time: string; - author: User; - content: string; - usersMetioned: UserMetioned[]; - }[]; + replies: ReplyWithLatestContent[]; nextCursor: number; } @@ -26,7 +21,7 @@ const REPLIES_PER_PAGE = parseInt(process.env.REPLIES_PER_PAGE ?? "10", 10); export const getKey = (id: number) => (pageIndex: number, previousPageData: PageData) => { // 已经到最后一页 - if (previousPageData && !previousPageData.data.length) return null; + if (previousPageData && !previousPageData.replies.length) return null; // 在首页时,没有 `previousPageData` if (pageIndex === 0) return `/${id}/replies?limit=${REPLIES_PER_PAGE}`; // 将游标添加到 API @@ -54,17 +49,17 @@ export default function InfiniteScrollReplies({ return ( <> c + a.data.length, 0) ?? 0} + dataLength={data?.reduce((c, a) => c + a.replies.length, 0) ?? 0} next={() => showPageButtons || setSize(size + 1)} - hasMore={(data?.[data.length - 1].data.length ?? 0) >= REPLIES_PER_PAGE} + hasMore={(data?.[data.length - 1].replies.length ?? 0) >= REPLIES_PER_PAGE} loader="" style={{ overflow: "inherit" }} scrollThreshold="1024px" > {data?.map( - (replies) => - replies.data?.map((reply) => ( - + (data) => + data.replies?.map((reply) => ( + )), )} diff --git a/packages/viewer/src/components/replies/MarkdownContent.tsx b/packages/viewer/src/components/replies/MarkdownContent.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/packages/viewer/src/components/replies/Reply.tsx b/packages/viewer/src/components/replies/Reply.tsx index 0bab0a3..74b825b 100644 --- a/packages/viewer/src/components/replies/Reply.tsx +++ b/packages/viewer/src/components/replies/Reply.tsx @@ -1,51 +1,50 @@ "use client"; import { useState } from "react"; -import type { User } from "@prisma/client"; +// import type { User } from "@prisma/client"; import Link from "next/link"; -import type { UserMetioned } from "@/lib/serialize-reply"; +// import type { UserMetioned } from "@/lib/serialize-reply"; import UserAvatar from "@/components/UserAvatar"; import UserInfo from "@/components/UserInfo"; import Content from "./Content"; import ContextViewer from "./ContextViewer"; +import type { ReplyWithLatestContent } from "@/lib/reply"; +import stringifyTime from "@/lib/time"; + export default function Reply({ - discussion, + post, reply, children, }: React.PropsWithChildren<{ - discussion: { id: number; authorId: number }; - reply: { - id?: number; - time: string; - author: User; - content: string; - usersMetioned: UserMetioned[]; - }; + post: { id: number; authorId: number }; + reply: ReplyWithLatestContent; }>) { const [userId, setUserId] = useState(null); + const snapshot = reply.snapshots[0] + return (
    - {reply.id && userId && ( + {reply.id !== -1 && userId && ( user.id === userId)!} + // userMetioned={reply.usersMetioned.find((user) => user.id === userId)!} key={userId} /> )}
    - +
    - - {reply.author.id === discussion.authorId ? ( + + {snapshot.author.id === post.authorId ? ( - {reply.time} - {reply.id !== undefined ? ( + {stringifyTime(reply.time)} + {reply.id !== -1 ? ( {children} - {reply.time} + {stringifyTime(reply.time)}
    diff --git a/packages/viewer/src/lib/luogu.ts b/packages/viewer/src/lib/luogu.ts index 197d4a1..d29efff 100644 --- a/packages/viewer/src/lib/luogu.ts +++ b/packages/viewer/src/lib/luogu.ts @@ -1,4 +1,6 @@ -export const getDiscussionUrl = (discussion: number, page?: number) => +import { Color, Forum } from "@prisma/client"; + +export const getPostUrl = (discussion: number, page?: number) => `https://www.luogu.com.cn/discuss/${discussion}${ page !== undefined ? `?page=${page}` : "" }`; @@ -11,18 +13,10 @@ export const getUserRealUrl = (user: number) => export const getUserAvatarUrl = (user: number) => `https://cdn.luogu.com.cn/upload/usericon/${user}.png`; -export const getForumUrl = (forum: string) => - `https://www.luogu.com.cn/discuss/lists?forumname=${forum}`; +export const getForumUrl = (forum: Forum) => + `https://www.luogu.com.cn/discuss/lists?forumname=${forum.slug}`; -export const getForumName = (forum: string) => - ({ - siteaffairs: "站务版", - problem: "题目总版", - academics: "学术版", - relevantaffairs: "灌水区", - service: "反馈、申请、工单专版", - miaomiaowu: "小黑屋", - })[forum] ?? forum; +export const getForumName = (forum: Forum) => forum.name; export const judgementUrl = "https://www.luogu.com.cn/judgement"; @@ -46,7 +40,7 @@ export function getUserIdFromUrl(target: URL) { (target.pathname.startsWith("/user/") && target.pathname.split("/")[2]) || ((target.pathname === "/space/show" && target.searchParams.get("uid")) as string), - 10, + 10 ); return Number.isNaN(uid) ? null : uid; } @@ -58,7 +52,7 @@ export function getDiscussionIdFromUrl(target: URL) { target.pathname.split("/")[2]) || ((target.pathname === "/discuss/show" && target.searchParams.get("postid")) as string), - 10, + 10 ); return Number.isNaN(discussionId) ? null : discussionId; } @@ -80,6 +74,31 @@ export function getDiscussionId(s: string) { (url.pathname === "/discuss/show" && url.searchParams.get("postid")) || ((url.pathname.startsWith("/discuss/") && url.pathname.split("/")[2]) as string), - 10, + 10 ); } + +export function getCheckmarkColor(ccfLevel: number) { + if (ccfLevel <= 5) return "#52c41a"; + if (ccfLevel <= 7) return "#3498db"; + return "#ffc116"; +} + +export function getNameClassByColor(color: Color) { + switch (color) { + case "Blue": + return "bluelight"; + case "Green": + return "green"; + case "Gray": + return "gray"; + case "Cheater": + return "brown"; + case "Orange": + return "orange"; + case "Purple": + return "purple"; + case "Red": + return "red"; + } +} diff --git a/packages/viewer/src/lib/post.ts b/packages/viewer/src/lib/post.ts index 6e10513..815ee1d 100644 --- a/packages/viewer/src/lib/post.ts +++ b/packages/viewer/src/lib/post.ts @@ -1,4 +1,6 @@ import { Prisma } from "@prisma/client"; +import { getReply } from "./reply"; +import { selectUser } from "./user"; export const selectPost = { withBasic: Prisma.validator()({ @@ -8,6 +10,8 @@ export const selectPost = { replyCount: true, }, }).select, + + // Not compatible to use `withLatestContent` here, because the `content` field withLatestSnapshotMeta: Prisma.validator()({ select: { snapshots: { @@ -15,7 +19,10 @@ export const selectPost = { time: true, title: true, forum: true, - author: true, + author: { + select: selectUser.withLatest, + }, + until: true, }, orderBy: { time: "desc" }, take: 1, @@ -27,11 +34,13 @@ export const selectPost = { takedown: { select: { reason: true, - submitter: true, + submitter: { select: selectUser.withLatest }, }, }, }, }).select, + + // Not compatible to use `withLatestSnapshotMeta` here, because the `content` field withLatestContent: Prisma.validator()({ select: { snapshots: { @@ -39,8 +48,11 @@ export const selectPost = { time: true, title: true, forum: true, - author: true, + author: { + select: selectUser.withLatest, + }, content: true, + until: true, }, orderBy: { time: "desc" }, take: 1, @@ -70,6 +82,16 @@ export const selectPostWithLatestContent = select: getPost.latestWithContent, }); +export const selectPostWithLatestReplies = + Prisma.validator()({ + select: { + ...selectPost.withLatestContent, + replies: { + select: getReply.latestWithContent, + }, + }, + }); + export type PostWithLatestSnapshotMeta = Prisma.PostGetPayload< typeof selectPostWithLatestSnapshotMeta >; @@ -77,3 +99,7 @@ export type PostWithLatestSnapshotMeta = Prisma.PostGetPayload< export type PostWithLatestContent = Prisma.PostGetPayload< typeof selectPostWithLatestContent >; + +export type PostWithLatestReplies = Prisma.PostGetPayload< + typeof selectPostWithLatestReplies +>; diff --git a/packages/viewer/src/lib/reply.ts b/packages/viewer/src/lib/reply.ts index 331216e..67106fb 100644 --- a/packages/viewer/src/lib/reply.ts +++ b/packages/viewer/src/lib/reply.ts @@ -1,4 +1,5 @@ import { Prisma } from "@prisma/client"; +import { selectUser } from "./user"; export const selectReply = { withBasic: Prisma.validator()({ @@ -13,7 +14,7 @@ export const selectReply = { snapshots: { select: { time: true, - author: true, + author: { select: selectUser.withLatest }, }, orderBy: { time: "desc" }, take: 1, @@ -25,11 +26,7 @@ export const selectReply = { takedown: { select: { reason: true, - submitter: { - select: { - userSnapshots: true, - }, - }, + submitter: { select: selectUser.withLatest }, }, }, }, @@ -39,7 +36,7 @@ export const selectReply = { snapshots: { select: { time: true, - author: true, + author: { select: selectUser.withLatest }, content: true, }, orderBy: { time: "desc" }, diff --git a/packages/viewer/src/lib/user.ts b/packages/viewer/src/lib/user.ts new file mode 100644 index 0000000..ab33ffb --- /dev/null +++ b/packages/viewer/src/lib/user.ts @@ -0,0 +1,24 @@ +import { Prisma } from "@prisma/client"; + +export const selectUser = { + withIdOnly: Prisma.validator()({ + select: { + id: true, + }, + }).select, + withLatest: Prisma.validator()({ + select: { + id: true, + userSnapshots: { + orderBy: { time: "desc" }, + take: 1, + }, + }, + }).select, +}; + +export const selectUserWithLatest = Prisma.validator()({ + select: selectUser.withLatest, +}); + +export type LatestUser = Prisma.UserGetPayload; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b57d350..96a5a43 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,6 +131,15 @@ importers: react-infinite-scroll-component: specifier: ^6.1.0 version: 6.1.0(react@18.2.0) + react-markdown: + specifier: ^9.0.0 + version: 9.0.0(@types/react@18.2.22)(react@18.2.0) + rehype-katex: + specifier: ^7.0.0 + version: 7.0.0 + remark-math: + specifier: ^6.0.0 + version: 6.0.0 socket.io-client: specifier: ^4.7.2 version: 4.7.2 @@ -656,6 +665,12 @@ packages: '@types/node': 20.6.3 dev: false + /@types/debug@4.1.9: + resolution: {integrity: sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow==} + dependencies: + '@types/ms': 0.7.32 + dev: false + /@types/eslint-scope@3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: @@ -674,6 +689,12 @@ packages: resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} dev: true + /@types/hast@3.0.1: + resolution: {integrity: sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==} + dependencies: + '@types/unist': 3.0.0 + dev: false + /@types/jsdom@21.1.3: resolution: {integrity: sha512-1zzqSP+iHJYV4lB3lZhNBa012pubABkj9yG/GuXuf6LZH1cSPIJBqFDrm5JX65HHt6VOnNYdTui/0ySerRbMgA==} dependencies: @@ -692,14 +713,22 @@ packages: /@types/katex@0.16.3: resolution: {integrity: sha512-CeVMX9EhVUW8MWnei05eIRks4D5Wscw/W9Byz1s3PA+yJvcdvq9SaDjiUKvRvEgjpdTyJMjQA43ae4KTwsvOPg==} - dev: true + + /@types/mdast@4.0.1: + resolution: {integrity: sha512-IlKct1rUTJ1T81d8OHzyop15kGv9A/ff7Gz7IJgrk6jDb4Udw77pCJ+vq8oxZf4Ghpm+616+i1s/LNg/Vh7d+g==} + dependencies: + '@types/unist': 3.0.0 + dev: false + + /@types/ms@0.7.32: + resolution: {integrity: sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==} + dev: false /@types/node@20.6.3: resolution: {integrity: sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA==} /@types/prop-types@15.7.6: resolution: {integrity: sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg==} - dev: true /@types/react-dom@18.2.7: resolution: {integrity: sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==} @@ -713,11 +742,9 @@ packages: '@types/prop-types': 15.7.6 '@types/scheduler': 0.16.3 csstype: 3.1.2 - dev: true /@types/scheduler@0.16.3: resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} - dev: true /@types/semver@7.5.2: resolution: {integrity: sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==} @@ -731,6 +758,10 @@ packages: resolution: {integrity: sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==} dev: true + /@types/unist@3.0.0: + resolution: {integrity: sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==} + dev: false + /@types/yauzl@2.10.0: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true @@ -869,6 +900,10 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: false + /@webassemblyjs/ast@1.11.6: resolution: {integrity: sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==} dependencies: @@ -1271,6 +1306,10 @@ packages: /b4a@1.6.4: resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} + /bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1424,6 +1463,10 @@ packages: engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: true + /character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + dev: false + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -1532,6 +1575,10 @@ packages: delayed-stream: 1.0.0 dev: false + /comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + dev: false + /commander@1.1.1: resolution: {integrity: sha512-71Rod2AhcH3JhkBikVpNd0pA+fWsmAaVoti6OR38T76chA7vE3pSerS0Jor4wDw+tOueD2zLVvFOw5H0Rcj7rA==} engines: {node: '>= 0.6.x'} @@ -1631,7 +1678,6 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - dev: true /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -1676,6 +1722,12 @@ packages: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} dev: false + /decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + dependencies: + character-entities: 2.0.2 + dev: false + /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -1753,7 +1805,6 @@ packages: /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - dev: true /detect-libc@2.0.2: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} @@ -1762,6 +1813,12 @@ packages: dev: false optional: true + /devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dependencies: + dequal: 2.0.3 + dev: false + /devtools-protocol@0.0.1179426: resolution: {integrity: sha512-KKC7IGwdOr7u9kTGgjUvGTov/z1s2H7oHi3zKCdR9eSDyCPia5CBi4aRhtp7d8uR7l0GS5UTDw3TjKGu5CqINg==} @@ -2429,6 +2486,10 @@ packages: dev: false optional: true + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: false + /extract-zip@2.0.1: resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} engines: {node: '>= 10.17.0'} @@ -2839,6 +2900,98 @@ packages: function-bind: 1.1.1 dev: true + /hast-util-from-dom@5.0.0: + resolution: {integrity: sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==} + dependencies: + '@types/hast': 3.0.1 + hastscript: 8.0.0 + web-namespaces: 2.0.1 + dev: false + + /hast-util-from-html-isomorphic@2.0.0: + resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} + dependencies: + '@types/hast': 3.0.1 + hast-util-from-dom: 5.0.0 + hast-util-from-html: 2.0.1 + unist-util-remove-position: 5.0.0 + dev: false + + /hast-util-from-html@2.0.1: + resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==} + dependencies: + '@types/hast': 3.0.1 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.1 + parse5: 7.1.2 + vfile: 6.0.1 + vfile-message: 4.0.2 + dev: false + + /hast-util-from-parse5@8.0.1: + resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==} + dependencies: + '@types/hast': 3.0.1 + '@types/unist': 3.0.0 + devlop: 1.1.0 + hastscript: 8.0.0 + property-information: 6.3.0 + vfile: 6.0.1 + vfile-location: 5.0.2 + web-namespaces: 2.0.1 + dev: false + + /hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + dependencies: + '@types/hast': 3.0.1 + dev: false + + /hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + dependencies: + '@types/hast': 3.0.1 + dev: false + + /hast-util-to-jsx-runtime@2.2.0: + resolution: {integrity: sha512-wSlp23N45CMjDg/BPW8zvhEi3R+8eRE1qFbjEyAUzMCzu2l1Wzwakq+Tlia9nkCtEl5mDxa7nKHsvYJ6Gfn21A==} + dependencies: + '@types/hast': 3.0.1 + '@types/unist': 3.0.0 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + property-information: 6.3.0 + space-separated-tokens: 2.0.2 + style-to-object: 0.4.2 + unist-util-position: 5.0.0 + vfile-message: 4.0.2 + dev: false + + /hast-util-to-text@4.0.0: + resolution: {integrity: sha512-EWiE1FSArNBPUo1cKWtzqgnuRQwEeQbQtnFJRYV1hb1BWDgrAlBU0ExptvZMM/KSA82cDpm2sFGf3Dmc5Mza3w==} + dependencies: + '@types/hast': 3.0.1 + '@types/unist': 3.0.0 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + dev: false + + /hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + dependencies: + '@types/hast': 3.0.1 + dev: false + + /hastscript@8.0.0: + resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} + dependencies: + '@types/hast': 3.0.1 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 6.3.0 + space-separated-tokens: 2.0.2 + dev: false + /highlight.js@11.8.0: resolution: {integrity: sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==} engines: {node: '>=12.0.0'} @@ -2851,6 +3004,10 @@ packages: whatwg-encoding: 2.0.0 dev: false + /html-url-attributes@3.0.0: + resolution: {integrity: sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==} + dev: false + /http-proxy-agent@5.0.0: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} @@ -2952,6 +3109,10 @@ packages: dev: false optional: true + /inline-style-parser@0.1.1: + resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} + dev: false + /internal-slot@1.0.5: resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} engines: {node: '>= 0.4'} @@ -3111,6 +3272,11 @@ packages: engines: {node: '>=8'} dev: true + /is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + dev: false + /is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} dev: false @@ -3429,6 +3595,10 @@ packages: wrap-ansi: 8.1.0 dev: true + /longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + dev: false + /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -3445,6 +3615,78 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + /mdast-util-from-markdown@2.0.0: + resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==} + dependencies: + '@types/mdast': 4.0.1 + '@types/unist': 3.0.0 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.0 + micromark-util-decode-numeric-character-reference: 2.0.0 + micromark-util-decode-string: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-math@3.0.0: + resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} + dependencies: + '@types/hast': 3.0.1 + '@types/mdast': 4.0.1 + devlop: 1.1.0 + longest-streak: 3.1.0 + mdast-util-from-markdown: 2.0.0 + mdast-util-to-markdown: 2.1.0 + unist-util-remove-position: 5.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-phrasing@4.0.0: + resolution: {integrity: sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA==} + dependencies: + '@types/mdast': 4.0.1 + unist-util-is: 6.0.0 + dev: false + + /mdast-util-to-hast@13.0.2: + resolution: {integrity: sha512-U5I+500EOOw9e3ZrclN3Is3fRpw8c19SMyNZlZ2IS+7vLsNzb2Om11VpIVOR+/0137GhZsFEF6YiKD5+0Hr2Og==} + dependencies: + '@types/hast': 3.0.1 + '@types/mdast': 4.0.1 + '@ungap/structured-clone': 1.2.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.0 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + dev: false + + /mdast-util-to-markdown@2.1.0: + resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + dependencies: + '@types/mdast': 4.0.1 + '@types/unist': 3.0.0 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.0.0 + mdast-util-to-string: 4.0.0 + micromark-util-decode-string: 2.0.0 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + dev: false + + /mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + dependencies: + '@types/mdast': 4.0.1 + dev: false + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -3454,6 +3696,193 @@ packages: engines: {node: '>= 8'} dev: true + /micromark-core-commonmark@2.0.0: + resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==} + dependencies: + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-factory-destination: 2.0.0 + micromark-factory-label: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-factory-title: 2.0.0 + micromark-factory-whitespace: 2.0.0 + micromark-util-character: 2.0.1 + micromark-util-chunked: 2.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-html-tag-name: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-subtokenize: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-extension-math@3.0.0: + resolution: {integrity: sha512-iJ2Q28vBoEovLN5o3GO12CpqorQRYDPT+p4zW50tGwTfJB+iv/VnB6Ini+gqa24K97DwptMBBIvVX6Bjk49oyQ==} + dependencies: + '@types/katex': 0.16.3 + devlop: 1.1.0 + katex: 0.16.8 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-destination@2.0.0: + resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + dependencies: + micromark-util-character: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-label@2.0.0: + resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-space@2.0.0: + resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + dependencies: + micromark-util-character: 2.0.1 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-title@2.0.0: + resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-whitespace@2.0.0: + resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-character@2.0.1: + resolution: {integrity: sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==} + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-chunked@2.0.0: + resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-classify-character@2.0.0: + resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + dependencies: + micromark-util-character: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-combine-extensions@2.0.0: + resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + dependencies: + micromark-util-chunked: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-decode-numeric-character-reference@2.0.0: + resolution: {integrity: sha512-pIgcsGxpHEtTG/rPJRz/HOLSqp5VTuIIjXlPI+6JSDlK2oljApusG6KzpS8AF0ENUMCHlC/IBb5B9xdFiVlm5Q==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-decode-string@2.0.0: + resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.0 + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + dev: false + + /micromark-util-html-tag-name@2.0.0: + resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + dev: false + + /micromark-util-normalize-identifier@2.0.0: + resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-resolve-all@2.0.0: + resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + dependencies: + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + dependencies: + micromark-util-character: 2.0.1 + micromark-util-encode: 2.0.0 + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-subtokenize@2.0.0: + resolution: {integrity: sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==} + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + dev: false + + /micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + dev: false + + /micromark@4.0.0: + resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + dependencies: + '@types/debug': 4.1.9 + debug: 4.3.4 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.0.1 + micromark-util-chunked: 2.0.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-decode-numeric-character-reference: 2.0.0 + micromark-util-encode: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-subtokenize: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -3975,6 +4404,10 @@ packages: react-is: 16.13.1 dev: true + /property-information@6.3.0: + resolution: {integrity: sha512-gVNZ74nqhRMiIUYWGQdosYetaKc83x8oT41a0LlV3AAFCAZwCpg4vmGkq8t34+cUhp3cnM4XDiU/7xlgK7HGrg==} + dev: false + /proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -4102,6 +4535,29 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true + /react-markdown@9.0.0(@types/react@18.2.22)(react@18.2.0): + resolution: {integrity: sha512-v6yNf3AB8GfJ8lCpUvzxAXKxgsHpdmWPlcVRQ6Nocsezp255E/IDrF31kLQsPJeB/cKto/geUwjU36wH784FCA==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + dependencies: + '@types/hast': 3.0.1 + '@types/react': 18.2.22 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.2.0 + html-url-attributes: 3.0.0 + mdast-util-to-hast: 13.0.2 + micromark-util-sanitize-uri: 2.0.0 + react: 18.2.0 + remark-parse: 11.0.0 + remark-rehype: 11.0.0 + unified: 11.0.3 + unist-util-visit: 5.0.0 + vfile: 6.0.1 + transitivePeerDependencies: + - supports-color + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -4167,6 +4623,50 @@ packages: set-function-name: 2.0.1 dev: true + /rehype-katex@7.0.0: + resolution: {integrity: sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q==} + dependencies: + '@types/hast': 3.0.1 + '@types/katex': 0.16.3 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.0 + katex: 0.16.8 + unist-util-visit-parents: 6.0.1 + vfile: 6.0.1 + dev: false + + /remark-math@6.0.0: + resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} + dependencies: + '@types/mdast': 4.0.1 + mdast-util-math: 3.0.0 + micromark-extension-math: 3.0.0 + unified: 11.0.3 + transitivePeerDependencies: + - supports-color + dev: false + + /remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + dependencies: + '@types/mdast': 4.0.1 + mdast-util-from-markdown: 2.0.0 + micromark-util-types: 2.0.0 + unified: 11.0.3 + transitivePeerDependencies: + - supports-color + dev: false + + /remark-rehype@11.0.0: + resolution: {integrity: sha512-vx8x2MDMcxuE4lBmQ46zYUDfcFMmvg80WYX+UNLeG6ixjdCCLcw1lrgAukwBTuOFsS78eoAedHGn9sNM0w7TPw==} + dependencies: + '@types/hast': 3.0.1 + '@types/mdast': 4.0.1 + mdast-util-to-hast: 13.0.2 + unified: 11.0.3 + vfile: 6.0.1 + dev: false + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -4530,6 +5030,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + /space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + dev: false + /split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -4657,6 +5161,12 @@ packages: engines: {node: '>=8'} dev: true + /style-to-object@0.4.2: + resolution: {integrity: sha512-1JGpfPB3lo42ZX8cuPrheZbfQ6kqPPnPHlKMyeRYtfKD+0jG+QsXgXN57O/dvJlzlB2elI6dGmrPnl5VPQFPaA==} + dependencies: + inline-style-parser: 0.1.1 + dev: false + /styled-jsx@5.1.1(react@18.2.0): resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -4858,6 +5368,14 @@ packages: punycode: 2.3.0 dev: false + /trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + dev: false + + /trough@2.1.0: + resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} + dev: false + /ts-api-utils@1.0.3(typescript@5.2.2): resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} engines: {node: '>=16.13.0'} @@ -4962,6 +5480,65 @@ packages: buffer: 5.7.1 through: 2.3.8 + /unified@11.0.3: + resolution: {integrity: sha512-jlCV402P+YDcFcB2VcN/n8JasOddqIiaxv118wNBoZXEhOn+lYG7BR4Bfg2BwxvlK58dwbuH2w7GX2esAjL6Mg==} + dependencies: + '@types/unist': 3.0.0 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.1.0 + vfile: 6.0.1 + dev: false + + /unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + dependencies: + '@types/unist': 3.0.0 + unist-util-is: 6.0.0 + dev: false + + /unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + dependencies: + '@types/unist': 3.0.0 + dev: false + + /unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + dependencies: + '@types/unist': 3.0.0 + dev: false + + /unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + dependencies: + '@types/unist': 3.0.0 + unist-util-visit: 5.0.0 + dev: false + + /unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + dependencies: + '@types/unist': 3.0.0 + dev: false + + /unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + dependencies: + '@types/unist': 3.0.0 + unist-util-is: 6.0.0 + dev: false + + /unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + dependencies: + '@types/unist': 3.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + dev: false + /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -5029,6 +5606,28 @@ packages: engines: {node: '>= 0.8'} dev: false + /vfile-location@5.0.2: + resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==} + dependencies: + '@types/unist': 3.0.0 + vfile: 6.0.1 + dev: false + + /vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + dependencies: + '@types/unist': 3.0.0 + unist-util-stringify-position: 4.0.0 + dev: false + + /vfile@6.0.1: + resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + dependencies: + '@types/unist': 3.0.0 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + dev: false + /w3c-xmlserializer@4.0.0: resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} engines: {node: '>=14'} @@ -5043,6 +5642,10 @@ packages: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + /web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + dev: false + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -5330,3 +5933,7 @@ packages: /zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} dev: false + + /zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + dev: false diff --git a/prisma/migrations/20230928144633_init/migration.sql b/prisma/migrations/20230928144633_init/migration.sql new file mode 100644 index 0000000..ce0ac75 --- /dev/null +++ b/prisma/migrations/20230928144633_init/migration.sql @@ -0,0 +1,221 @@ +-- CreateEnum +CREATE TYPE "Color" AS ENUM ('Cheater', 'Gray', 'Blue', 'Green', 'Orange', 'Red', 'Purple'); + +-- CreateTable +CREATE TABLE "User" ( + "id" INTEGER NOT NULL, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Forum" ( + "slug" TEXT NOT NULL, + "name" TEXT NOT NULL, + + CONSTRAINT "Forum_pkey" PRIMARY KEY ("slug") +); + +-- CreateTable +CREATE TABLE "Post" ( + "id" INTEGER NOT NULL, + "time" TIMESTAMP(0) NOT NULL, + "replyCount" INTEGER NOT NULL, + + CONSTRAINT "Post_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Reply" ( + "id" INTEGER NOT NULL, + "postId" INTEGER NOT NULL, + "time" TIMESTAMP(0) NOT NULL, + + CONSTRAINT "Reply_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Activity" ( + "id" INTEGER NOT NULL, + "type" INTEGER NOT NULL, + "time" TIMESTAMP(0) NOT NULL, + "userId" INTEGER NOT NULL, + "content" TEXT NOT NULL, + + CONSTRAINT "Activity_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Paste" ( + "id" CHAR(8) NOT NULL, + "time" TIMESTAMP(0) NOT NULL, + "userId" INTEGER NOT NULL, + + CONSTRAINT "Paste_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Judgement" ( + "time" TIMESTAMP(0) NOT NULL, + "userId" INTEGER NOT NULL, + "content" TEXT NOT NULL, + + CONSTRAINT "Judgement_pkey" PRIMARY KEY ("time","userId") +); + +-- CreateTable +CREATE TABLE "UserSnapshot" ( + "userId" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "badge" TEXT, + "isAdmin" BOOLEAN NOT NULL, + "isBanned" BOOLEAN NOT NULL, + "isRoot" BOOLEAN, + "color" "Color" NOT NULL, + "ccfLevel" INTEGER NOT NULL, + "until" TIMESTAMP(3) NOT NULL, + "time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "UserSnapshot_pkey" PRIMARY KEY ("userId","time") +); + +-- CreateTable +CREATE TABLE "PostSnapshot" ( + "postId" INTEGER NOT NULL, + "authorId" INTEGER NOT NULL, + "time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "until" TIMESTAMP(3) NOT NULL, + "title" TEXT NOT NULL, + "forumSlug" TEXT NOT NULL, + "content" TEXT NOT NULL, + + CONSTRAINT "PostSnapshot_pkey" PRIMARY KEY ("postId","time") +); + +-- CreateTable +CREATE TABLE "ReplySnapshot" ( + "replyId" INTEGER NOT NULL, + "authorId" INTEGER NOT NULL, + "time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "until" TIMESTAMP(3) NOT NULL, + "content" TEXT NOT NULL, + + CONSTRAINT "ReplySnapshot_pkey" PRIMARY KEY ("replyId","time") +); + +-- CreateTable +CREATE TABLE "PasteSnapshot" ( + "pasteId" CHAR(8) NOT NULL, + "time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "until" TIMESTAMP(3) NOT NULL, + "public" BOOLEAN NOT NULL, + "data" TEXT, + + CONSTRAINT "PasteSnapshot_pkey" PRIMARY KEY ("pasteId","time") +); + +-- CreateTable +CREATE TABLE "PostTakedown" ( + "postId" INTEGER NOT NULL, + "submitterId" INTEGER NOT NULL, + "reason" TEXT NOT NULL, + + CONSTRAINT "PostTakedown_pkey" PRIMARY KEY ("postId") +); + +-- CreateTable +CREATE TABLE "ReplyTakedown" ( + "replyId" INTEGER NOT NULL, + "submitterId" INTEGER NOT NULL, + "reason" TEXT NOT NULL, + + CONSTRAINT "ReplyTakedown_pkey" PRIMARY KEY ("replyId") +); + +-- CreateTable +CREATE TABLE "ActivityTakedown" ( + "activityId" INTEGER NOT NULL, + "submitterId" INTEGER NOT NULL, + "reason" TEXT NOT NULL, + + CONSTRAINT "ActivityTakedown_pkey" PRIMARY KEY ("activityId") +); + +-- CreateIndex +CREATE INDEX "Reply_postId_idx" ON "Reply"("postId"); + +-- CreateIndex +CREATE INDEX "Paste_userId_idx" ON "Paste"("userId"); + +-- CreateIndex +CREATE INDEX "Judgement_time_idx" ON "Judgement"("time" DESC); + +-- CreateIndex +CREATE INDEX "Judgement_userId_idx" ON "Judgement"("userId"); + +-- CreateIndex +CREATE INDEX "UserSnapshot_userId_idx" ON "UserSnapshot"("userId"); + +-- CreateIndex +CREATE INDEX "PostSnapshot_postId_idx" ON "PostSnapshot"("postId"); + +-- CreateIndex +CREATE INDEX "PostSnapshot_authorId_idx" ON "PostSnapshot"("authorId"); + +-- CreateIndex +CREATE INDEX "ReplySnapshot_replyId_idx" ON "ReplySnapshot"("replyId"); + +-- CreateIndex +CREATE INDEX "ReplySnapshot_authorId_idx" ON "ReplySnapshot"("authorId"); + +-- AddForeignKey +ALTER TABLE "Reply" ADD CONSTRAINT "Reply_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Activity" ADD CONSTRAINT "Activity_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Paste" ADD CONSTRAINT "Paste_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Judgement" ADD CONSTRAINT "Judgement_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UserSnapshot" ADD CONSTRAINT "UserSnapshot_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostSnapshot" ADD CONSTRAINT "PostSnapshot_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostSnapshot" ADD CONSTRAINT "PostSnapshot_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostSnapshot" ADD CONSTRAINT "PostSnapshot_forumSlug_fkey" FOREIGN KEY ("forumSlug") REFERENCES "Forum"("slug") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ReplySnapshot" ADD CONSTRAINT "ReplySnapshot_replyId_fkey" FOREIGN KEY ("replyId") REFERENCES "Reply"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ReplySnapshot" ADD CONSTRAINT "ReplySnapshot_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PasteSnapshot" ADD CONSTRAINT "PasteSnapshot_pasteId_fkey" FOREIGN KEY ("pasteId") REFERENCES "Paste"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostTakedown" ADD CONSTRAINT "PostTakedown_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PostTakedown" ADD CONSTRAINT "PostTakedown_submitterId_fkey" FOREIGN KEY ("submitterId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ReplyTakedown" ADD CONSTRAINT "ReplyTakedown_replyId_fkey" FOREIGN KEY ("replyId") REFERENCES "Reply"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ReplyTakedown" ADD CONSTRAINT "ReplyTakedown_submitterId_fkey" FOREIGN KEY ("submitterId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ActivityTakedown" ADD CONSTRAINT "ActivityTakedown_activityId_fkey" FOREIGN KEY ("activityId") REFERENCES "Activity"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ActivityTakedown" ADD CONSTRAINT "ActivityTakedown_submitterId_fkey" FOREIGN KEY ("submitterId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file