diff --git a/app/api/comments/[id]/route.ts b/app/api/comments/[id]/route.ts index a404a0c2..e4d4d107 100644 --- a/app/api/comments/[id]/route.ts +++ b/app/api/comments/[id]/route.ts @@ -1,5 +1,5 @@ import { currentUser } from '@clerk/nextjs' -import { desc, eq } from 'drizzle-orm' +import { asc, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' @@ -20,14 +20,14 @@ export async function GET(req: NextRequest, { params }: Params) { const data = await db .select({ id: comments.id, + userId: comments.userId, userInfo: comments.userInfo, body: comments.body, createdAt: comments.createdAt, }) .from(comments) .where(eq(comments.postId, postId)) - .orderBy(desc(comments.createdAt)) - .limit(100) + .orderBy(asc(comments.createdAt)) return NextResponse.json( data.map( @@ -46,7 +46,7 @@ export async function GET(req: NextRequest, { params }: Params) { const CreateCommentSchema = z.object({ body: z.object({ blockId: z.string().optional(), - text: z.string().min(1), + text: z.string().min(1).max(500), }), }) @@ -73,6 +73,7 @@ export async function POST(req: NextRequest, { params }: Params) { const commentData = { postId, + userId: user.id, body, userInfo: { firstName: user.firstName, @@ -80,10 +81,7 @@ export async function POST(req: NextRequest, { params }: Params) { imageUrl: user.imageUrl, }, } - const { insertId } = await db.insert(comments).values({ - userId: user.id, - ...commentData, - }) + const { insertId } = await db.insert(comments).values(commentData) return NextResponse.json({ ...commentData, diff --git a/components/Commentable.tsx b/components/Commentable.tsx index 9ee0df0c..8570c7d8 100644 --- a/components/Commentable.tsx +++ b/components/Commentable.tsx @@ -1,12 +1,17 @@ 'use client' +import 'dayjs/locale/zh-cn' + import { SignedIn, SignedOut, SignInButton, useUser } from '@clerk/nextjs' import { clsxm } from '@zolplay/utils' +import dayjs from 'dayjs' +import relativeTime from 'dayjs/plugin/relativeTime' import { AnimatePresence, motion } from 'framer-motion' import Image from 'next/image' import { usePathname } from 'next/navigation' import React from 'react' import { useMutation } from 'react-query' +import TextareaAutosize from 'react-textarea-autosize' import { useSnapshot } from 'valtio' import { addComment, blogPostState } from '~/app/(main)/blog/blog-post.state' @@ -16,10 +21,19 @@ import { UserArrowLeftIcon, XSquareIcon, } from '~/assets' +import { Markdown } from '~/components/Markdown' import { Button } from '~/components/ui/Button' import { HoverCard } from '~/components/ui/HoverCard' -import { type CommentDto } from '~/db/dto/comment.dto' +import { + type CommentDto, + type PostIDLessCommentDto, +} from '~/db/dto/comment.dto' import { url } from '~/lib' +import { parseDisplayName } from '~/lib/string' + +dayjs.extend(relativeTime) + +const MAX_COMMENT_LENGTH = 500 type CommentableProps = { blockId?: string @@ -29,15 +43,15 @@ export function Commentable({ blockId }: CommentableProps) { const { postId, comments } = useSnapshot(blogPostState) const { user: me } = useUser() const [isCommenting, setIsCommenting] = React.useState(false) - const [comment, setComment] = React.useState('') const currentComments = React.useMemo( () => comments.filter((c) => c.body.blockId === blockId), [comments, blockId] ) + const formRef = React.useRef(null) const { mutate: createComment, isLoading } = useMutation( ['comment', postId], - async () => { + async (comment: string) => { const res = await fetch(`/api/comments/${postId}`, { method: 'POST', headers: { @@ -46,7 +60,7 @@ export function Commentable({ blockId }: CommentableProps) { body: JSON.stringify({ body: { blockId, - text: comment.trim(), + text: comment, } satisfies CommentDto['body'], }), }) @@ -56,22 +70,27 @@ export function Commentable({ blockId }: CommentableProps) { { onSuccess: (data) => { addComment(data) - setComment('') + + window.dispatchEvent(new CustomEvent('clear-comment')) }, } ) - const onSubmit = (e?: React.FormEvent) => { - e?.preventDefault() - if (!comment.trim()) return - - createComment() - } - const onCommentKeydown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && e.metaKey) { - e.preventDefault() - onSubmit() - } - } + const onSubmit = React.useCallback( + (e?: React.FormEvent) => { + e?.preventDefault() + + if (e?.currentTarget || formRef.current) { + const formData = new FormData(e?.currentTarget ?? formRef.current!) + const comment = formData.get('comment') as string + if (!comment.trim() || comment.length > MAX_COMMENT_LENGTH) return + + createComment(comment) + } + }, + [createComment] + ) + + const isMe = (comment: PostIDLessCommentDto) => comment.userId === me?.id return ( @@ -99,89 +118,102 @@ export function Commentable({ blockId }: CommentableProps) { animate={{ opacity: 1, y: 0, scale: 1 }} exit={{ opacity: 0, y: 10, scale: 0.96 }} > - - {me && ( - <> - -
- {currentComments.length > 0 && ( -
-

- 所有评论 -

-
- )} -
-
- -