CommentThread.svelte
1<script lang="ts">
2 import type { ReviewThread } from '$lib/types/index';
3 import { resolveThread, deleteThread } from '$lib/stores/review';
4 import CommentEditor from './CommentEditor.svelte';
5
6 interface Props {
7 thread: ReviewThread;
8 }
9
10 let { thread }: Props = $props();
11 let replying = $state(false);
12
13 const typeIcons: Record<string, string> = {
14 comment: '💬',
15 suggestion: '💡',
16 question: '❓'
17 };
18
19 function formatTime(iso: string): string {
20 const d = new Date(iso);
21 return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
22 }
23</script>
24
25<div class="border-l-2 {thread.outdated ? 'border-accent-yellow/60 opacity-75' : thread.resolved ? 'border-border-strong opacity-60' : 'border-accent-blue'} bg-panel/50 rounded-r-lg my-1 mx-2">
26 {#if thread.outdated}
27 <div class="px-3 py-1.5 bg-accent-yellow/20 border-b border-border flex items-center gap-2">
28 <span class="text-xs font-medium text-accent-yellow">Outdated</span>
29 <span class="text-xs text-accent-yellow/70">The code this comment was on has changed</span>
30 </div>
31 {/if}
32 {#each thread.comments as comment, i}
33 <div class="px-3 py-2 {i > 0 ? 'border-t border-border' : ''}">
34 <div class="flex items-center gap-2 mb-1">
35 <span class="text-sm">{typeIcons[comment.type]}</span>
36 <span class="text-xs font-medium text-secondary capitalize">{comment.type}</span>
37 <span class="text-xs text-muted">{formatTime(comment.createdAt)}</span>
38 <span class="text-xs text-faint">Line {comment.lineNumber}</span>
39 </div>
40 <p class="text-sm text-primary whitespace-pre-wrap">{comment.body}</p>
41 </div>
42 {/each}
43
44 <div class="px-3 py-2 border-t border-border flex gap-2">
45 <button
46 class="text-xs text-tertiary hover:text-accent-blue transition-colors"
47 onclick={() => (replying = !replying)}
48 >
49 Reply
50 </button>
51 <button
52 class="text-xs text-tertiary hover:text-accent-green transition-colors"
53 onclick={() => resolveThread(thread.id)}
54 >
55 {thread.resolved ? 'Unresolve' : 'Resolve'}
56 </button>
57 <button
58 class="text-xs text-tertiary hover:text-accent-red transition-colors"
59 onclick={() => deleteThread(thread.id)}
60 >
61 Delete
62 </button>
63 </div>
64
65 {#if replying}
66 <div class="px-2 pb-2">
67 <CommentEditor
68 filePath={thread.filePath}
69 lineNumber={thread.lineNumber}
70 side={thread.side}
71 threadId={thread.id}
72 onclose={() => (replying = false)}
73 />
74 </div>
75 {/if}
76</div>