Adds DiffSplitView component for side-by-side diff mode, whitespace-only change filtering, inline review comments with thread/gutter cell UI, diff preferences persistence, and write-file API support for in-browser editing. Backend: hideWhitespace param on git diff endpoint, write_file route.
93 lines
3.0 KiB
TypeScript
93 lines
3.0 KiB
TypeScript
import { useState } from 'react';
|
|
import { MessageSquare, Pencil, Trash2 } from 'lucide-react';
|
|
import type { DiffComment } from '@/stores/useDiffCommentStore';
|
|
import { InlineReviewEditor } from './InlineReviewEditor';
|
|
|
|
interface InlineReviewThreadProps {
|
|
comments: DiffComment[];
|
|
onEditComment: (id: string, body: string) => void;
|
|
onDeleteComment: (id: string) => void;
|
|
}
|
|
|
|
export function InlineReviewThread({
|
|
comments,
|
|
onEditComment,
|
|
onDeleteComment,
|
|
}: InlineReviewThreadProps) {
|
|
const [expanded, setExpanded] = useState(true);
|
|
const [editingId, setEditingId] = useState<string | null>(null);
|
|
const [editBody, setEditBody] = useState('');
|
|
|
|
if (comments.length === 0) return null;
|
|
|
|
const handleStartEdit = (id: string, body: string) => {
|
|
setEditingId(id);
|
|
setEditBody(body);
|
|
};
|
|
|
|
const handleSaveEdit = (body: string) => {
|
|
if (editingId) {
|
|
onEditComment(editingId, body);
|
|
setEditingId(null);
|
|
}
|
|
};
|
|
|
|
const handleCancelEdit = () => {
|
|
setEditingId(null);
|
|
};
|
|
|
|
return (
|
|
<div className="ml-1 border-l-2 border-blue-400/40 pl-2 my-1">
|
|
<button
|
|
type="button"
|
|
onClick={() => setExpanded(!expanded)}
|
|
className="flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground mb-0.5"
|
|
>
|
|
<MessageSquare size={10} />
|
|
<span>{comments.length} comment{comments.length > 1 ? 's' : ''}</span>
|
|
<span className="text-[9px]">{expanded ? '▲' : '▼'}</span>
|
|
</button>
|
|
|
|
{expanded && (
|
|
<div className="space-y-1">
|
|
{comments.map((comment) => (
|
|
<div key={comment.id} className="text-xs">
|
|
{editingId === comment.id ? (
|
|
<InlineReviewEditor
|
|
initialBody={editBody}
|
|
onSave={handleSaveEdit}
|
|
onCancel={handleCancelEdit}
|
|
/>
|
|
) : (
|
|
<div className="flex items-start gap-1 group">
|
|
<span className="flex-1 text-foreground/90 leading-relaxed whitespace-pre-wrap">
|
|
{comment.body}
|
|
</span>
|
|
<div className="hidden group-hover:flex items-center gap-0.5 shrink-0 mt-0.5">
|
|
<button
|
|
type="button"
|
|
onClick={() => handleStartEdit(comment.id, comment.body)}
|
|
className="p-0.5 rounded hover:bg-muted text-muted-foreground"
|
|
title="Edit"
|
|
>
|
|
<Pencil size={10} />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => onDeleteComment(comment.id)}
|
|
className="p-0.5 rounded hover:bg-muted text-muted-foreground hover:text-destructive"
|
|
title="Delete"
|
|
>
|
|
<Trash2 size={10} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|