Add update method to ReviewCommentRepository, updateReviewComment tRPC procedure, and inline edit UI in CommentThread. Edit button appears on user-authored comments (not agent comments) when unresolved. Uses the existing CommentForm with a new initialValue prop.
173 lines
5.3 KiB
TypeScript
173 lines
5.3 KiB
TypeScript
import { useRef, useEffect } from "react";
|
|
import { MessageSquarePlus } from "lucide-react";
|
|
import type { DiffLine, ReviewComment } from "./types";
|
|
import { CommentThread } from "./CommentThread";
|
|
import { CommentForm } from "./CommentForm";
|
|
import type { TokenizedLine } from "./use-syntax-highlight";
|
|
|
|
interface LineWithCommentsProps {
|
|
line: DiffLine;
|
|
lineKey: number;
|
|
lineComments: ReviewComment[];
|
|
isCommenting: boolean;
|
|
onStartComment: () => void;
|
|
onCancelComment: () => void;
|
|
onSubmitComment: (body: string) => void;
|
|
onResolveComment: (commentId: string) => void;
|
|
onUnresolveComment: (commentId: string) => void;
|
|
onReplyComment?: (parentCommentId: string, body: string) => void;
|
|
onEditComment?: (commentId: string, body: string) => void;
|
|
/** Syntax-highlighted tokens for this line (if available) */
|
|
tokens?: TokenizedLine;
|
|
}
|
|
|
|
export function LineWithComments({
|
|
line,
|
|
lineKey: _lineKey,
|
|
lineComments,
|
|
isCommenting,
|
|
onStartComment,
|
|
onCancelComment,
|
|
onSubmitComment,
|
|
onResolveComment,
|
|
onUnresolveComment,
|
|
onReplyComment,
|
|
onEditComment,
|
|
tokens,
|
|
}: LineWithCommentsProps) {
|
|
const formRef = useRef<HTMLTextAreaElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (isCommenting) {
|
|
formRef.current?.focus();
|
|
}
|
|
}, [isCommenting]);
|
|
|
|
const bgClass =
|
|
line.type === "added"
|
|
? "bg-diff-add-bg"
|
|
: line.type === "removed"
|
|
? "bg-diff-remove-bg"
|
|
: "";
|
|
|
|
const gutterBgClass =
|
|
line.type === "added"
|
|
? "bg-diff-add-bg"
|
|
: line.type === "removed"
|
|
? "bg-diff-remove-bg"
|
|
: "bg-muted/30";
|
|
|
|
const prefix =
|
|
line.type === "added" ? "+" : line.type === "removed" ? "-" : " ";
|
|
|
|
const textColorClass =
|
|
line.type === "added"
|
|
? "text-diff-add-fg"
|
|
: line.type === "removed"
|
|
? "text-diff-remove-fg"
|
|
: "";
|
|
|
|
return (
|
|
<>
|
|
<tr
|
|
className={`group ${bgClass} hover:ring-1 hover:ring-inset hover:ring-primary/10`}
|
|
>
|
|
{/* Line numbers */}
|
|
<td
|
|
className={`w-[72px] min-w-[72px] select-none text-right text-muted-foreground pr-1 ${gutterBgClass} align-top`}
|
|
>
|
|
<div className="flex items-center justify-end gap-0">
|
|
<span className="w-8 inline-block text-right text-[11px] leading-5">
|
|
{line.oldLineNumber ?? ""}
|
|
</span>
|
|
<span className="w-8 inline-block text-right text-[11px] leading-5">
|
|
{line.newLineNumber ?? ""}
|
|
</span>
|
|
</div>
|
|
</td>
|
|
|
|
{/* Comment button gutter */}
|
|
<td className={`w-6 min-w-6 ${gutterBgClass} align-top`}>
|
|
{lineComments.length > 0 ? (
|
|
<div className="relative flex items-center justify-center h-5">
|
|
<button
|
|
className="flex items-center justify-center group/comment"
|
|
onClick={onStartComment}
|
|
title={`${lineComments.length} comment${lineComments.length > 1 ? "s" : ""} — click to reply`}
|
|
>
|
|
<span className="h-2 w-2 rounded-full bg-primary group-hover/comment:hidden" />
|
|
<MessageSquarePlus className="h-3.5 w-3.5 text-primary hidden group-hover/comment:block" />
|
|
{lineComments.length > 1 && (
|
|
<span className="absolute -top-0.5 -right-0.5 text-[8px] text-primary font-bold leading-none">
|
|
{lineComments.length}
|
|
</span>
|
|
)}
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<button
|
|
className="opacity-0 group-hover:opacity-50 transition-opacity p-0.5 hover:!opacity-100 hover:text-primary text-muted-foreground"
|
|
onClick={onStartComment}
|
|
title="Add comment"
|
|
>
|
|
<MessageSquarePlus className="h-3.5 w-3.5" />
|
|
</button>
|
|
)}
|
|
</td>
|
|
|
|
{/* Code content */}
|
|
<td className="pl-1 pr-3 align-top">
|
|
<pre
|
|
className={`leading-5 whitespace-pre-wrap break-all ${tokens ? "" : textColorClass}`}
|
|
>
|
|
<span className="select-none text-muted-foreground/60">
|
|
{prefix}
|
|
</span>
|
|
{tokens
|
|
? tokens.map((token, i) => (
|
|
<span key={i} style={{ color: token.color }}>
|
|
{token.content}
|
|
</span>
|
|
))
|
|
: line.content}
|
|
</pre>
|
|
</td>
|
|
</tr>
|
|
|
|
{/* Existing comments on this line */}
|
|
{lineComments.length > 0 && (
|
|
<tr data-comment-id={lineComments.find((c) => !c.parentCommentId)?.id}>
|
|
<td
|
|
colSpan={3}
|
|
className="px-3 py-2 bg-muted/20 border-y border-border/50"
|
|
>
|
|
<CommentThread
|
|
comments={lineComments}
|
|
onResolve={onResolveComment}
|
|
onUnresolve={onUnresolveComment}
|
|
onReply={onReplyComment}
|
|
onEdit={onEditComment}
|
|
/>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
|
|
{/* Inline comment form */}
|
|
{isCommenting && (
|
|
<tr>
|
|
<td
|
|
colSpan={3}
|
|
className="px-3 py-2 bg-diff-hunk-bg border-y border-status-active-border"
|
|
>
|
|
<CommentForm
|
|
ref={formRef}
|
|
onSubmit={onSubmitComment}
|
|
onCancel={onCancelComment}
|
|
/>
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</>
|
|
);
|
|
}
|