|
650 | 650 | animation: none; |
651 | 651 | } |
652 | 652 |
|
| 653 | + /* Deleted comment styling */ |
| 654 | + .ck-comment-deleted { |
| 655 | + opacity: 0.6; |
| 656 | + } |
| 657 | +
|
| 658 | + .ck-comment-deleted .ck-comment-inner { |
| 659 | + background: #fafafa; |
| 660 | + border-radius: var(--ck-radius); |
| 661 | + padding: 12px; |
| 662 | + border: 1px dashed #e5e7eb; |
| 663 | + } |
| 664 | +
|
653 | 665 | .ck-replies { |
654 | 666 | margin-top: 16px; |
655 | 667 | margin-left: 56px; /* Align with parent comment content */ |
|
1654 | 1666 | const hasReplies = comment.replies && comment.replies.length > 0; |
1655 | 1667 | const replyCount = hasReplies ? comment.replies.length : 0; |
1656 | 1668 | const isExpanded = this.state.expandedReplies.has(comment.id); |
| 1669 | + const isPlaceholder = comment.isPlaceholder || false; |
| 1670 | + |
| 1671 | + // Render placeholder for deleted comments |
| 1672 | + if (isPlaceholder) { |
| 1673 | + return ` |
| 1674 | + <div class="ck-comment ck-comment-deleted" data-id="${comment.id}"> |
| 1675 | + <div class="ck-comment-inner"> |
| 1676 | + <div class="ck-avatar" style="background: #f3f4f6; color: #9ca3af; border-color: #e5e7eb;"> |
| 1677 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| 1678 | + <circle cx="12" cy="12" r="10"></circle> |
| 1679 | + <line x1="15" y1="9" x2="9" y2="15"></line> |
| 1680 | + <line x1="9" y1="9" x2="15" y2="15"></line> |
| 1681 | + </svg> |
| 1682 | + </div> |
| 1683 | + <div class="ck-comment-content"> |
| 1684 | + <div class="ck-comment-body" style="color: #9ca3af; font-style: italic;"> |
| 1685 | + ${this.escapeHtml(comment.content)} |
| 1686 | + </div> |
| 1687 | + </div> |
| 1688 | + </div> |
| 1689 | + ${hasReplies ? ` |
| 1690 | + <div class="ck-replies"> |
| 1691 | + ${comment.replies.map(r => this.renderComment(r, depth + 1)).join('')} |
| 1692 | + </div> |
| 1693 | + ` : ''} |
| 1694 | + </div> |
| 1695 | + `; |
| 1696 | + } |
1657 | 1697 |
|
| 1698 | + // Regular comment rendering |
1658 | 1699 | return ` |
1659 | 1700 | <div class="ck-comment" data-id="${comment.id}"> |
1660 | 1701 | <div class="ck-comment-inner"> |
|
2118 | 2159 | buildTree(comments) { |
2119 | 2160 | const map = new Map(); |
2120 | 2161 | const roots = []; |
| 2162 | + const orphanedReplies = []; // Track comments with missing parents |
2121 | 2163 |
|
2122 | 2164 | // Helper to parse dates securely for sorting |
2123 | 2165 | const getDate = (d) => new Date(d.includes('Z') || d.includes('+') ? d : d.replace(' ', 'T') + 'Z'); |
2124 | 2166 |
|
| 2167 | + // First pass: build map of all existing comments |
2125 | 2168 | comments.forEach(c => map.set(c.id, { ...c, replies: [] })); |
| 2169 | + |
| 2170 | + // Second pass: attach replies to parents or collect orphans |
2126 | 2171 | comments.forEach(c => { |
2127 | 2172 | const comment = map.get(c.id); |
2128 | | - if (c.parent_id && map.has(c.parent_id)) { |
2129 | | - map.get(c.parent_id).replies.push(comment); |
| 2173 | + if (c.parent_id) { |
| 2174 | + if (map.has(c.parent_id)) { |
| 2175 | + // Parent exists, attach to it |
| 2176 | + map.get(c.parent_id).replies.push(comment); |
| 2177 | + } else { |
| 2178 | + // Parent is missing (deleted/rejected/spam), track as orphan |
| 2179 | + orphanedReplies.push({ parentId: c.parent_id, comment }); |
| 2180 | + } |
2130 | 2181 | } else { |
| 2182 | + // Top-level comment |
2131 | 2183 | roots.push(comment); |
2132 | 2184 | } |
2133 | 2185 | }); |
2134 | 2186 |
|
| 2187 | + // Third pass: Create placeholder comments for missing parents |
| 2188 | + if (orphanedReplies.length > 0) { |
| 2189 | + // Group orphans by parent ID |
| 2190 | + const orphansByParent = new Map(); |
| 2191 | + orphanedReplies.forEach(({ parentId, comment }) => { |
| 2192 | + if (!orphansByParent.has(parentId)) { |
| 2193 | + orphansByParent.set(parentId, []); |
| 2194 | + } |
| 2195 | + orphansByParent.get(parentId).push(comment); |
| 2196 | + }); |
| 2197 | + |
| 2198 | + // Create placeholder for each missing parent |
| 2199 | + orphansByParent.forEach((replies, parentId) => { |
| 2200 | + // Create a placeholder comment |
| 2201 | + const placeholder = { |
| 2202 | + id: parentId, |
| 2203 | + author_name: '[deleted]', |
| 2204 | + author_email_hash: null, |
| 2205 | + content: '[deleted comment]', |
| 2206 | + parent_id: null, |
| 2207 | + likes: 0, |
| 2208 | + user_liked: false, |
| 2209 | + created_at: replies[0].created_at, // Use first reply's timestamp |
| 2210 | + replies: replies, |
| 2211 | + isPlaceholder: true, // Flag to identify placeholders |
| 2212 | + }; |
| 2213 | + |
| 2214 | + map.set(parentId, placeholder); |
| 2215 | + roots.push(placeholder); |
| 2216 | + }); |
| 2217 | + } |
| 2218 | + |
2135 | 2219 | // Sort roots: Newest first |
2136 | 2220 | roots.sort((a, b) => getDate(b.created_at) - getDate(a.created_at)); |
2137 | 2221 |
|
|
0 commit comments