Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/app/api/notifications/read/route.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import { countUnreadNotifications } from "@/db/notification-read-queries"
import { countUnreadNotifications, findCommentsWithPostByIds, findNotificationTargetById } from "@/db/notification-read-queries"
import { markNotificationAsRead } from "@/db/notification-queries"
import { apiSuccess, createUserRouteHandler, readJsonBody, requireStringField } from "@/lib/api-route"
import { notificationEventBus } from "@/lib/notification-event-bus"
import { invalidateNotificationUserCache } from "@/lib/notification-redis-cache"
import { revalidatePostCommentCache } from "@/lib/post-detail-cache"
import { revalidateUserSurfaceCache } from "@/lib/user-surface"

export const POST = createUserRouteHandler(async ({ request, currentUser }) => {
const body = await readJsonBody(request)
const notificationId = requireStringField(body, "notificationId", "缺少通知 ID")

await markNotificationAsRead(currentUser.id, notificationId)
const [notification] = await Promise.all([
findNotificationTargetById(currentUser.id, notificationId),
markNotificationAsRead(currentUser.id, notificationId),
])

if (notification?.relatedType === "COMMENT") {
const [comment] = await findCommentsWithPostByIds([notification.relatedId])
if (comment?.post) {
revalidatePostCommentCache({
postId: comment.post.id,
slug: comment.post.slug,
})
}
}

await invalidateNotificationUserCache(currentUser.id)
const unreadNotificationCount = await countUnreadNotifications(currentUser.id)
revalidateUserSurfaceCache(currentUser.id)
Expand Down
15 changes: 10 additions & 5 deletions src/components/comment/comment-thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -365,11 +365,6 @@ export function CommentThread({ threadId, comments, flatComments = [], postId, p
const highlightedFromSearch = searchParams.get("highlight")
if (highlightedFromSearch) {
triggerCommentHighlight(highlightedFromSearch)
const nextSearchParams = new URLSearchParams(searchParams.toString())
nextSearchParams.delete("highlight")
const nextSearch = nextSearchParams.toString()
const hash = typeof window === "undefined" ? "" : window.location.hash
window.history.replaceState(null, "", `${pathname}${nextSearch ? `?${nextSearch}` : ""}${hash}`)
return
}

Expand Down Expand Up @@ -437,6 +432,16 @@ export function CommentThread({ threadId, comments, flatComments = [], postId, p
stableAttempts = isSettled ? stableAttempts + 1 : 0

if (stableAttempts >= COMMENT_ANCHOR_SCROLL_STABLE_ATTEMPTS && elapsed >= COMMENT_ANCHOR_SCROLL_MIN_SETTLE_MS) {
const currentUrl = new URL(window.location.href)
if (currentUrl.searchParams.get("highlight") === highlightedCommentId) {
currentUrl.searchParams.delete("highlight")
currentUrl.hash = `comment-${highlightedCommentId}`
window.history.replaceState(
window.history.state,
"",
`${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`,
)
}
return
}

Expand Down
13 changes: 8 additions & 5 deletions src/components/notification/notification-list-item.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use client"

import Link from "next/link"
import { useRouter } from "next/navigation"
import { useState } from "react"
import { Trash2 } from "lucide-react"
Expand All @@ -25,12 +24,16 @@ export function NotificationListItem({ id, href, isRead, typeLabel, title, conte
const [isDeleting, setIsDeleting] = useState(false)

async function handleClick(event: React.MouseEvent<HTMLAnchorElement>) {
if (isRead || isPending) {
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || isRead) {
return
}

event.preventDefault()

if (isPending) {
return
}

setIsPending(true)

try {
Expand All @@ -46,7 +49,7 @@ export function NotificationListItem({ id, href, isRead, typeLabel, title, conte
return
}

router.push(href)
window.location.assign(href)
} finally {
setIsPending(false)
}
Expand Down Expand Up @@ -102,14 +105,14 @@ export function NotificationListItem({ id, href, isRead, typeLabel, title, conte
</Button>
</div>
</div>
<Link href={href} onClick={handleClick} className="mt-3 block rounded-lg outline-hidden focus-visible:ring-2 focus-visible:ring-ring">
<a href={href} onClick={handleClick} className="mt-3 block rounded-lg outline-hidden focus-visible:ring-2 focus-visible:ring-ring">
<h2 className="text-base font-semibold">{title}</h2>
<p className="mt-2 text-sm leading-7 text-muted-foreground">{content}</p>
<div className="mt-3 flex items-center justify-between gap-3 text-xs text-muted-foreground">
<span>来源:{senderName}</span>
<span>{isDeleting ? "删除中..." : isPending ? "跳转中..." : "点击查看详情"}</span>
</div>
</Link>
</a>
</article>
)
}
13 changes: 13 additions & 0 deletions src/db/notification-read-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ export function countUnreadNotifications(userId: number) {
})
}

export function findNotificationTargetById(userId: number, notificationId: string) {
return prisma.notification.findFirst({
where: {
id: notificationId,
userId,
},
select: {
relatedType: true,
relatedId: true,
},
})
}

export async function countUnreadNotificationsByUserIds(userIds: number[]) {
const normalizedUserIds = [...new Set(userIds.filter((userId) => Number.isInteger(userId) && userId > 0))]

Expand Down