跟随 Catppuccin 配色迁移,清理 src/ 中绕过 paper-* 语义 token 的 硬编码 dark:[#hex] 任意值,改用 Tailwind 默认灰阶(gray-700/800), 它们随主题切换自然适配。 - post.rs 状态徽章:dark:bg-[#333]/dark:text-[#9b9c9d] → gray-800/gray-400/500 (同步更新测试断言) - comments/* 与 skeletons/* 的 dark:[#2a2a2a]/[#2e2e33]/[#333]/[#5a5a62] → 对应 gray-600/700/800 - trash.rs 状态灯发光圈 rgba 由旧鼠尾草绿(92,122,94) 改 Latte green(64,160,43) - ui.rs BTN_SECONDARY 注释更新为新 Teal 强调色值 注:src/pages/admin/posts.rs 的 clippy redundant_closure 报错为既有问题, 与本次配色迁移无关,不在范围内。
118 lines
4.1 KiB
Rust
118 lines
4.1 KiB
Rust
//! 单条评论项组件
|
||
//!
|
||
//! 展示已审核通过的评论,支持展开/收起回复表单。
|
||
|
||
use dioxus::prelude::*;
|
||
|
||
use crate::components::comments::form::CommentForm;
|
||
use crate::components::comments::section::CommentContext;
|
||
use crate::models::comment::PublicComment;
|
||
|
||
/// 单条已审核评论组件。
|
||
///
|
||
/// Props:
|
||
/// - `comment`:已审核评论数据
|
||
/// - `post_id`:所属文章 ID
|
||
///
|
||
/// 关键行为:
|
||
/// - 点击"回复"按钮切换该评论下方的回复表单
|
||
/// - 最大递归深度限制为 20,超过后隐藏回复按钮
|
||
#[component]
|
||
pub fn CommentItem(comment: PublicComment, post_id: i32) -> Element {
|
||
let ctx: CommentContext = use_context();
|
||
let mut active_reply = ctx.active_reply;
|
||
let refresh_trigger = ctx.refresh_trigger;
|
||
|
||
// 孤儿评论按顶层展示
|
||
let depth = if comment.parent_id.is_none() && comment.depth > 0 {
|
||
0
|
||
} else {
|
||
comment.depth
|
||
};
|
||
|
||
let indent = depth.min(6) * 24;
|
||
|
||
let is_replying = active_reply() == Some(comment.id);
|
||
let show_reply = depth < 20;
|
||
|
||
let _ = refresh_trigger;
|
||
|
||
// 作者名展示为链接或普通文本
|
||
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", 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-gray-800",
|
||
}
|
||
|
||
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",
|
||
title: "{comment.created_at_iso}",
|
||
"{comment.created_at}"
|
||
}
|
||
}
|
||
|
||
div {
|
||
class: "prose prose-sm dark:prose-invert max-w-none text-paper-secondary md-content",
|
||
dangerous_inner_html: comment.content_html.as_deref().unwrap_or(""),
|
||
}
|
||
|
||
div { class: "flex items-center gap-3 mt-2",
|
||
if show_reply {
|
||
button {
|
||
class: "text-xs text-paper-tertiary hover:text-paper-accent transition-colors cursor-pointer",
|
||
aria_label: "回复 {comment.author_name} 的评论",
|
||
onclick: move |_| {
|
||
if is_replying {
|
||
active_reply.set(None);
|
||
} else {
|
||
active_reply.set(Some(comment.id));
|
||
}
|
||
},
|
||
if is_replying {
|
||
"取消回复"
|
||
} else {
|
||
"回复"
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
if is_replying {
|
||
CommentForm {
|
||
post_id,
|
||
parent_id: Some(comment.id),
|
||
parent_indent: Some(indent),
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|