yggdrasil/components/comments/
list.rs1use leptos::prelude::*;
6
7use crate::components::comments::item::CommentItem;
8use crate::components::comments::pending_item::PendingCommentItem;
9use crate::models::comment::PublicComment;
10use crate::utils::comment_storage::PendingComment;
11
12#[derive(Clone, PartialEq)]
14enum MergedComment {
15 Approved(PublicComment),
16 Pending(PendingComment),
17}
18
19impl MergedComment {
20 fn key(&self) -> (bool, i64) {
23 match self {
24 MergedComment::Approved(c) => (false, c.id),
25 MergedComment::Pending(c) => (true, c.id),
26 }
27 }
28}
29
30fn merge_and_treeify(
38 approved: Vec<PublicComment>,
39 pending: Vec<PendingComment>,
40) -> Vec<MergedComment> {
41 use std::collections::{HashMap, HashSet};
42
43 let all: Vec<MergedComment> = approved
44 .into_iter()
45 .map(MergedComment::Approved)
46 .chain(pending.into_iter().map(MergedComment::Pending))
47 .collect();
48
49 let all_ids: HashSet<i64> = all
50 .iter()
51 .map(|c| match c {
52 MergedComment::Approved(c) => c.id,
53 MergedComment::Pending(c) => c.id,
54 })
55 .collect();
56
57 let mut children_map: HashMap<Option<i64>, Vec<MergedComment>> = HashMap::new();
59 for comment in all {
60 let parent_id = match &comment {
61 MergedComment::Approved(c) => c.parent_id,
62 MergedComment::Pending(c) => c.parent_id,
63 };
64 let effective_parent = match parent_id {
65 Some(pid) if !all_ids.contains(&pid) => None,
66 _ => parent_id,
67 };
68 children_map
69 .entry(effective_parent)
70 .or_default()
71 .push(comment);
72 }
73
74 for children in children_map.values_mut() {
76 children.sort_by(|a, b| {
77 let time_a = match a {
78 MergedComment::Approved(c) => c.created_at_iso.as_str(),
79 MergedComment::Pending(c) => c.created_at.as_str(),
80 };
81 let time_b = match b {
82 MergedComment::Approved(c) => c.created_at_iso.as_str(),
83 MergedComment::Pending(c) => c.created_at.as_str(),
84 };
85 time_a.cmp(time_b)
86 });
87 }
88
89 fn dfs(
91 parent_id: Option<i64>,
92 children_map: &HashMap<Option<i64>, Vec<MergedComment>>,
93 result: &mut Vec<MergedComment>,
94 ) {
95 if let Some(children) = children_map.get(&parent_id) {
96 for child in children {
97 result.push(child.clone());
98 let child_id = match child {
99 MergedComment::Approved(c) => Some(c.id),
100 MergedComment::Pending(c) => Some(c.id),
101 };
102 dfs(child_id, children_map, result);
103 }
104 }
105 }
106
107 let mut result = Vec::new();
108 dfs(None, &children_map, &mut result);
109 result
110}
111
112#[component]
122pub fn CommentList(
123 #[prop(into)] comments: Signal<Vec<PublicComment>>,
124 #[prop(into)] pending: Signal<Vec<PendingComment>>,
125 post_id: i32,
126) -> impl IntoView {
127 let merged = Memo::new(move |_| merge_and_treeify(comments.get(), pending.get()));
128
129 view! {
130 <div class="comment-list">
132 <For
133 each=move || merged.get()
134 key=|item: &MergedComment| item.key()
135 children=move |item: MergedComment| match item {
136 MergedComment::Approved(comment) => {
137 view! { <CommentItem comment=comment post_id=post_id/> }.into_any()
138 }
139 MergedComment::Pending(comment) => {
140 view! { <PendingCommentItem comment=comment post_id=post_id/> }.into_any()
141 }
142 }
143 />
144 </div>
145 }
146}