mirror of
https://github.com/ngthanhvu/kr-phim.git
synced 2026-08-10 16:47:46 +00:00
feat: enhance comment section with reply functionality and pinning feature
- Added ability to pin comments for admin users. - Implemented nested replies with a recursive component for better organization. - Improved comment loading with time-ago formatting for replies. - Refactored comment fetching logic to include replies in a structured format. - Updated UI to reflect changes in comment state (pinned, anonymous). - Added loading indicators for comment submission and reply actions. - Enhanced error handling for comment operations.
This commit is contained in:
@@ -59,9 +59,29 @@ export default defineEventHandler(async (event) => {
|
||||
userVotes = Object.fromEntries(votes.map(v => [v.commentId, v.vote]))
|
||||
}
|
||||
|
||||
let replies: any[] = []
|
||||
if (commentIds.length > 0) {
|
||||
replies = await db
|
||||
function mapReply(rep: any) {
|
||||
return {
|
||||
id: rep.id,
|
||||
userId: rep.userId,
|
||||
userName: rep.anonymous ? 'Ẩn danh' : (rep.userName || 'Ẩn danh'),
|
||||
userAvatar: rep.anonymous ? null : rep.userAvatar,
|
||||
userRole: rep.userRole,
|
||||
parentId: rep.parentId,
|
||||
content: rep.content,
|
||||
pinned: rep.pinned || false,
|
||||
spoiler: rep.spoiler || false,
|
||||
anonymous: rep.anonymous || false,
|
||||
likeCount: rep.likeCount,
|
||||
dislikeCount: rep.dislikeCount,
|
||||
createdAt: rep.createdAt,
|
||||
userVote: userVotes[rep.id] || 0,
|
||||
replies: [],
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchRepliesByParentIds(parentIds: number[]): Promise<any[]> {
|
||||
if (parentIds.length === 0) return []
|
||||
const fetched = await db
|
||||
.select({
|
||||
id: comments.id,
|
||||
userId: comments.userId,
|
||||
@@ -81,55 +101,54 @@ export default defineEventHandler(async (event) => {
|
||||
.leftJoin(users, eq(comments.userId, users.id))
|
||||
.where(
|
||||
and(
|
||||
or(...commentIds.map(id => eq(comments.parentId, id)))
|
||||
or(...parentIds.map(id => eq(comments.parentId, id)))
|
||||
)
|
||||
)
|
||||
.orderBy(comments.createdAt)
|
||||
return fetched
|
||||
}
|
||||
|
||||
const repliesByParent: Record<number, typeof replies> = {}
|
||||
for (const reply of replies) {
|
||||
let allReplies: any[] = []
|
||||
if (commentIds.length > 0) {
|
||||
let currentParentIds = [...commentIds]
|
||||
const maxDepth = 10
|
||||
let depth = 0
|
||||
while (currentParentIds.length > 0 && depth < maxDepth) {
|
||||
const replies = await fetchRepliesByParentIds(currentParentIds)
|
||||
if (replies.length === 0) break
|
||||
allReplies = allReplies.concat(replies)
|
||||
currentParentIds = replies.map(r => r.id)
|
||||
depth++
|
||||
}
|
||||
}
|
||||
|
||||
const repliesByParent: Record<number, any[]> = {}
|
||||
for (const reply of allReplies) {
|
||||
const parentId = reply.parentId!
|
||||
if (!repliesByParent[parentId]) {
|
||||
repliesByParent[parentId] = []
|
||||
}
|
||||
repliesByParent[parentId].push(reply)
|
||||
repliesByParent[parentId].push(mapReply(reply))
|
||||
}
|
||||
|
||||
function attachChildren(mappedReply: any): any {
|
||||
const children = repliesByParent[mappedReply.id] || []
|
||||
return {
|
||||
...mappedReply,
|
||||
replies: children.map(attachChildren),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
items: results.map(r => ({
|
||||
id: r.id,
|
||||
userId: r.userId,
|
||||
...r,
|
||||
userName: r.anonymous ? 'Ẩn danh' : (r.userName || 'Ẩn danh'),
|
||||
userAvatar: r.anonymous ? null : r.userAvatar,
|
||||
userRole: r.userRole,
|
||||
source: r.source,
|
||||
slug: r.slug,
|
||||
movieName: r.movieName,
|
||||
content: r.content,
|
||||
pinned: r.pinned || false,
|
||||
spoiler: r.spoiler || false,
|
||||
anonymous: r.anonymous || false,
|
||||
likeCount: r.likeCount,
|
||||
dislikeCount: r.dislikeCount,
|
||||
createdAt: r.createdAt,
|
||||
userVote: userVotes[r.id] || 0,
|
||||
replies: (repliesByParent[r.id] || []).map(rep => ({
|
||||
id: rep.id,
|
||||
userId: rep.userId,
|
||||
userName: rep.anonymous ? 'Ẩn danh' : (rep.userName || 'Ẩn danh'),
|
||||
userAvatar: rep.anonymous ? null : rep.userAvatar,
|
||||
userRole: rep.userRole,
|
||||
parentId: rep.parentId,
|
||||
content: rep.content,
|
||||
pinned: rep.pinned || false,
|
||||
spoiler: rep.spoiler || false,
|
||||
anonymous: rep.anonymous || false,
|
||||
likeCount: rep.likeCount,
|
||||
dislikeCount: rep.dislikeCount,
|
||||
createdAt: rep.createdAt,
|
||||
userVote: userVotes[rep.id] || 0,
|
||||
})),
|
||||
replies: (repliesByParent[r.id] || []).map(attachChildren),
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import mysql from 'mysql2/promise'
|
||||
import { comments } from '../../database/schema'
|
||||
import { getTokenFromEvent, verifyToken } from '../../utils/auth'
|
||||
|
||||
@@ -28,30 +29,35 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
const db = useDb()
|
||||
const dbUrl = process.env.DATABASE_URL || 'mysql://cinek:cinekpassword@localhost:3306/cinek'
|
||||
const conn = await mysql.createConnection(dbUrl)
|
||||
|
||||
const result = await db.insert(comments).values({
|
||||
userId: payload.id,
|
||||
source: source || '',
|
||||
slug,
|
||||
movieName: movieName || null,
|
||||
content: content.trim(),
|
||||
parentId: parentId ? Number(parentId) : null,
|
||||
spoiler: spoiler ? true : false,
|
||||
anonymous: anonymous ? true : false,
|
||||
})
|
||||
try {
|
||||
const [result] = await conn.execute(
|
||||
'INSERT INTO comments (user_id, source, slug, movie_name, content, parent_id, spoiler, anonymous) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[payload.id, source || '', slug, movieName || null, content.trim(), parentId ? Number(parentId) : null, spoiler ? 1 : 0, anonymous ? 1 : 0]
|
||||
)
|
||||
|
||||
return {
|
||||
id: result.insertId,
|
||||
userId: payload.id,
|
||||
source: source || '',
|
||||
slug,
|
||||
movieName: movieName || null,
|
||||
content: content.trim(),
|
||||
spoiler: spoiler ? true : false,
|
||||
anonymous: anonymous ? true : false,
|
||||
parentId: parentId ? Number(parentId) : null,
|
||||
likeCount: 0,
|
||||
dislikeCount: 0,
|
||||
const insertId = (result as any).insertId
|
||||
if (!insertId) {
|
||||
throw createError({ statusCode: 500, message: 'Không thể tạo bình luận' })
|
||||
}
|
||||
|
||||
return {
|
||||
id: Number(insertId),
|
||||
userId: payload.id,
|
||||
source: source || '',
|
||||
slug,
|
||||
movieName: movieName || null,
|
||||
content: content.trim(),
|
||||
spoiler: false,
|
||||
anonymous: anonymous ? true : false,
|
||||
parentId: parentId ? Number(parentId) : null,
|
||||
likeCount: 0,
|
||||
dislikeCount: 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
} finally {
|
||||
await conn.end()
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user