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>