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 form;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod item;
|
pub mod item;
|
||||||
|
pub mod pending_item;
|
||||||
pub mod actions;
|
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 || {
|
use_future(move || {
|
||||||
let pending = ctx.pending_comments;
|
let mut pending = ctx.pending_comments;
|
||||||
async move {
|
async move {
|
||||||
let ids: Vec<i64> = pending().iter().map(|c| c.id).collect();
|
let ids: Vec<i64> = pending().iter().map(|c| c.id).collect();
|
||||||
if ids.is_empty() {
|
if ids.is_empty() {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ pub struct AuthorInfo {
|
|||||||
pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct PendingComment {
|
pub struct PendingComment {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub parent_id: Option<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 key = post_id.to_string();
|
||||||
|
|
||||||
let comments = map.remove(&key).unwrap_or_default();
|
let comments = map.remove(&key).unwrap_or_default();
|
||||||
|
let original_len = comments.len();
|
||||||
let non_expired: Vec<PendingComment> = comments
|
let non_expired: Vec<PendingComment> = comments
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|c| !is_expired(&c.stored_at))
|
.filter(|c| !is_expired(&c.stored_at))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let pruned = non_expired.len() != comments.len();
|
let pruned = non_expired.len() != original_len;
|
||||||
if !non_expired.is_empty() {
|
if !non_expired.is_empty() {
|
||||||
map.insert(key, non_expired.clone());
|
map.insert(key, non_expired.clone());
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user