/** * @copyright NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { Component, Input, Output, EventEmitter, signal, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import type { Comment } from '@library/shared-types'; import { PrimaryBadge } from '@library/shared-types'; import { AuthService } from '../../services/auth.service'; import { SanitizeService } from '../../services/sanitize.service'; import { ReportModalComponent } from '../report-modal/report-modal.component'; @Component({ selector: 'app-comment-display', standalone: true, imports: [CommonModule, FormsModule, ReportModalComponent], template: `
@if (comments().length > 0) { @for (comment of comments(); track comment.id) {
@if (comment.user.avatar) { } {{ comment.user.username }} @if (comment.user.primaryBadge) { @if (comment.user.primaryBadge === PrimaryBadge.STAFF && comment.user.isStaff) { Staff } @if (comment.user.primaryBadge === PrimaryBadge.MOD && comment.user.isMod) { Mod } @if (comment.user.primaryBadge === PrimaryBadge.VIP && comment.user.isVip) { VIP } @if (comment.user.primaryBadge === PrimaryBadge.DISCORD && comment.user.inDiscord) { Discord } } {{ formatDate(comment.createdAt) }} @if (canEditComment(comment)) { } @if (canDeleteComment(comment)) { } @if (canReportComment(comment)) { }
@if (editingCommentId() === comment.id) {
} @else { @if (comment.hasPendingReports) {
[comment pending admin review]
} @else {
} }
} } @else {
No comments yet. Be the first to comment!
}
@if (showReportModal()) { } `, styles: [` .comments-section { margin-top: 1rem; } .comment { background: #f9fafb; padding: 0.75rem; border-radius: 0.375rem; margin-bottom: 0.75rem; } .comment-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; } .comment-avatar { width: 2rem; height: 2rem; border-radius: 50%; object-fit: cover; } .comment-author { font-weight: 600; color: #1f2937; } .discord-badge, .vip-badge, .mod-badge, .staff-badge { padding: 0.125rem 0.5rem; border-radius: 0.25rem; font-size: 0.75rem; font-weight: 600; } .discord-badge { background: #5865f2; color: white; } .vip-badge { background: #fbbf24; color: #78350f; } .mod-badge { background: #10b981; color: white; } .staff-badge { background: #8b5cf6; color: white; } .comment-date { font-size: 0.75rem; color: #6b7280; } .comment-content { font-size: 0.9rem; color: #4b5563; } .comment-pending-review { font-size: 0.9rem; color: #9b59b6; font-style: italic; padding: 0.5rem; background: #f3e8ff; border-radius: 0.25rem; } .comment-edit-form { margin-top: 0.5rem; } .comment-edit-form textarea { width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 0.375rem; font-family: inherit; resize: vertical; } .comment-edit-actions { display: flex; gap: 0.5rem; margin-top: 0.5rem; } .no-comments { text-align: center; color: #6b7280; padding: 2rem; font-style: italic; } .btn { padding: 0.25rem 0.75rem; border: none; border-radius: 0.25rem; cursor: pointer; font-size: 0.875rem; transition: all 0.2s; } .btn-xs { padding: 0.125rem 0.5rem; font-size: 0.75rem; } .btn-primary { background: #3b82f6; color: white; } .btn-primary:hover { background: #2563eb; } .btn-secondary { background: #6b7280; color: white; } .btn-secondary:hover { background: #4b5563; } .btn-danger { background: #ef4444; color: white; } .btn-danger:hover { background: #dc2626; } .btn-warning { background: #f59e0b; color: white; } .btn-warning:hover { background: #d97706; } `] }) export class CommentDisplayComponent { private readonly authService = inject(AuthService); readonly sanitizeService = inject(SanitizeService); // Expose PrimaryBadge enum for template readonly PrimaryBadge = PrimaryBadge; @Input({ required: true }) comments = signal([]); @Output() edit = new EventEmitter<{ commentId: string; content: string }>(); @Output() delete = new EventEmitter(); editingCommentId = signal(null); editCommentContent = ''; showReportModal = signal(false); reportingCommentId = signal(''); formatDate(date: Date | string): string { const d = new Date(date); return d.toLocaleDateString('en-GB', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }); } canEditComment(comment: Comment): boolean { const user = this.authService.user(); if (!user) return false; return comment.userId === user.id || this.authService.isAdmin(); } canDeleteComment(comment: Comment): boolean { const user = this.authService.user(); if (!user) return false; return comment.userId === user.id || this.authService.isAdmin(); } canReportComment(comment: Comment): boolean { const user = this.authService.user(); if (!user) return false; // Users can report comments they didn't write (but not their own) return comment.userId !== user.id; } startEdit(comment: Comment): void { this.editingCommentId.set(comment.id); this.editCommentContent = comment.rawContent ?? comment.content; } saveEdit(commentId: string): void { this.edit.emit({ commentId, content: this.editCommentContent }); this.cancelEdit(); } cancelEdit(): void { this.editingCommentId.set(null); this.editCommentContent = ''; } openReportModal(comment: Comment): void { this.reportingCommentId.set(comment.id); this.showReportModal.set(true); } closeReportModal(): void { this.showReportModal.set(false); this.reportingCommentId.set(''); } }