feat(comments): add PendingCommentItem component
Renders pending (unapproved) comments with: - opacity-70 for visual distinction - amber '审核中' badge - Client-side content_md rendering (HTML escape + newline→br) - No reply button (server rejects replies to pending parents) - Same depth/indent logic as approved comments
This commit is contained in:
parent
12a91e3b8e
commit
8ae3299b3e
@ -2,4 +2,5 @@ pub mod section;
|
||||
pub mod form;
|
||||
pub mod list;
|
||||
pub mod item;
|
||||
pub mod pending_item;
|
||||
pub mod actions;
|
||||
|
||||
71
src/components/comments/pending_item.rs
Normal file
71
src/components/comments/pending_item.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::hooks::comment_storage::{PendingComment, render_pending_content};
|
||||
|
||||
#[component]
|
||||
pub fn PendingCommentItem(comment: PendingComment, post_id: i32) -> Element {
|
||||
let _ = post_id;
|
||||
|
||||
let depth = if comment.parent_id.is_none() && comment.depth > 0 {
|
||||
0
|
||||
} else {
|
||||
comment.depth
|
||||
};
|
||||
|
||||
let indent = depth.min(6) * 24;
|
||||
let content_html = render_pending_content(&comment.content_md);
|
||||
|
||||
let author_element = match &comment.author_url {
|
||||
Some(url) if !url.is_empty() => rsx! {
|
||||
a {
|
||||
href: "{url}",
|
||||
rel: "nofollow noopener",
|
||||
target: "_blank",
|
||||
class: "font-medium text-paper-primary hover:text-paper-accent transition-colors",
|
||||
"{comment.author_name}"
|
||||
}
|
||||
},
|
||||
_ => rsx! {
|
||||
span { class: "font-medium text-paper-primary",
|
||||
"{comment.author_name}"
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: "py-4 opacity-70",
|
||||
style: "margin-left: {indent}px",
|
||||
|
||||
div { class: "flex gap-3",
|
||||
img {
|
||||
src: "{comment.avatar_url}",
|
||||
alt: "{comment.author_name} 的头像",
|
||||
loading: "lazy",
|
||||
decoding: "async",
|
||||
class: "w-8 h-8 rounded-full shrink-0 mt-0.5 bg-gray-200 dark:bg-[#2a2a2a]",
|
||||
}
|
||||
|
||||
div { class: "flex-1 min-w-0",
|
||||
div { class: "flex items-center gap-1.5 text-sm mb-1.5 flex-wrap",
|
||||
{author_element}
|
||||
span { class: "text-paper-tertiary", "·" }
|
||||
span {
|
||||
class: "text-paper-tertiary",
|
||||
"刚刚"
|
||||
}
|
||||
span {
|
||||
class: "inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400",
|
||||
"审核中"
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
class: "prose prose-sm dark:prose-invert max-w-none text-paper-secondary",
|
||||
dangerous_inner_html: "{content_html}",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,7 +27,7 @@ pub fn CommentSection(post_id: i32) -> Element {
|
||||
});
|
||||
|
||||
use_future(move || {
|
||||
let pending = ctx.pending_comments;
|
||||
let mut pending = ctx.pending_comments;
|
||||
async move {
|
||||
let ids: Vec<i64> = pending().iter().map(|c| c.id).collect();
|
||||
if ids.is_empty() {
|
||||
|
||||
@ -13,7 +13,7 @@ pub struct AuthorInfo {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PendingComment {
|
||||
pub id: i64,
|
||||
pub parent_id: Option<i64>,
|
||||
@ -108,12 +108,13 @@ pub fn load_pending_comments(post_id: i32) -> Vec<PendingComment> {
|
||||
let key = post_id.to_string();
|
||||
|
||||
let comments = map.remove(&key).unwrap_or_default();
|
||||
let original_len = comments.len();
|
||||
let non_expired: Vec<PendingComment> = comments
|
||||
.into_iter()
|
||||
.filter(|c| !is_expired(&c.stored_at))
|
||||
.collect();
|
||||
|
||||
let pruned = non_expired.len() != comments.len();
|
||||
let pruned = non_expired.len() != original_len;
|
||||
if !non_expired.is_empty() {
|
||||
map.insert(key, non_expired.clone());
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user