feat(comments): add pending_comments to CommentContext and sync on mount

- Extend CommentContext with pending_comments Signal
- Load pending comments from localStorage on provider init
- Run check_pending_status on mount to prune non-pending entries
- Pass both approved and pending comments to CommentList
- Include pending count in section heading
This commit is contained in:
xfy 2026-06-11 14:30:49 +08:00
parent 0f3d9fc25c
commit 40cfd44d3a

View File

@ -1,21 +1,50 @@
use dioxus::prelude::*;
use crate::api::comments::{get_comments, CommentTreeResponse};
use crate::api::comments::{check_pending_status, get_comments, CommentTreeResponse};
use crate::components::comments::form::CommentForm;
use crate::components::comments::list::CommentList;
use crate::components::skeletons::comment_skeleton::CommentListSkeleton;
use crate::hooks::comment_storage::{self, PendingComment};
#[derive(Clone, Copy)]
pub struct CommentContext {
pub active_reply: Signal<Option<i64>>,
pub refresh_trigger: Signal<bool>,
pub pending_comments: Signal<Vec<PendingComment>>,
}
#[component]
pub fn CommentSection(post_id: i32) -> Element {
let ctx = use_context_provider(|| CommentContext {
active_reply: Signal::new(None),
refresh_trigger: Signal::new(false),
let ctx = use_context_provider(|| {
let pending: Vec<PendingComment> = comment_storage::load_pending_comments(post_id);
comment_storage::prune_all_expired();
CommentContext {
active_reply: Signal::new(None),
refresh_trigger: Signal::new(false),
pending_comments: Signal::new(pending),
}
});
use_future(move || {
let pending = ctx.pending_comments;
async move {
let ids: Vec<i64> = pending().iter().map(|c| c.id).collect();
if ids.is_empty() {
return;
}
if let Ok(statuses) = check_pending_status(ids).await {
let to_remove: Vec<i64> = statuses
.into_iter()
.filter(|s| s.status != "pending")
.map(|s| s.id)
.collect();
if !to_remove.is_empty() {
comment_storage::remove_pending_ids(post_id, &to_remove);
pending.write().retain(|c| !to_remove.contains(&c.id));
}
}
}
});
let comments_resource = use_server_future(move || {
@ -27,21 +56,28 @@ pub fn CommentSection(post_id: i32) -> Element {
match data.as_ref().map(|r| r.as_ref()) {
Some(Ok(CommentTreeResponse { comments, count })) => {
let count = *count;
let approved_count = *count;
let pending_count = ctx.pending_comments.read().len() as i64;
let total_count = approved_count + pending_count;
let has_any = approved_count > 0 || pending_count > 0;
rsx! {
div { class: "space-y-8",
h2 { class: "text-xl font-bold text-paper-primary",
"评论区 ({count})"
"评论区 ({total_count})"
}
CommentForm { post_id, parent_id: None }
if comments.is_empty() {
if !has_any {
p { class: "text-paper-tertiary text-center py-8",
"暂无评论,成为第一个评论的人吧!"
}
} else {
CommentList { comments: comments.clone(), post_id }
CommentList {
comments: comments.clone(),
pending: ctx.pending_comments.read().clone(),
post_id,
}
}
}
}