d62b51b0be
* fix: route controlled diff comment updates * fix: address review comments overview feedback * fix: address automated review feedback * fix: keep failed controlled edits open * fix: guard controlled comment deletes * fix: compare controlled delete handler * test: cover controlled update rerender
112 lines
3.3 KiB
TypeScript
112 lines
3.3 KiB
TypeScript
import { useMemo, useCallback } from "react";
|
|
import type { DiffComment, DiffCommentUpdate } from "./types";
|
|
|
|
/** Build a DiffComment object from common parameters */
|
|
export function buildDiffComment(params: {
|
|
filePath: string;
|
|
sessionId: string;
|
|
startLine: number;
|
|
endLine: number;
|
|
side: DiffComment["side"];
|
|
text: string;
|
|
codeContent?: string;
|
|
}): DiffComment {
|
|
return {
|
|
id: `${params.filePath}-${Date.now()}`,
|
|
source: "diff",
|
|
sessionId: params.sessionId,
|
|
filePath: params.filePath,
|
|
startLine: Math.min(params.startLine, params.endLine),
|
|
endLine: Math.max(params.startLine, params.endLine),
|
|
side: params.side,
|
|
codeContent: params.codeContent ?? "",
|
|
text: params.text,
|
|
createdAt: new Date().toISOString(),
|
|
status: "pending",
|
|
};
|
|
}
|
|
|
|
/** Compute the set of line numbers that have comments */
|
|
export function useCommentedLines(comments: DiffComment[]): Set<number> {
|
|
return useMemo(() => {
|
|
const set = new Set<number>();
|
|
for (const c of comments) {
|
|
for (let l = c.startLine; l <= c.endLine; l++) set.add(l);
|
|
}
|
|
return set;
|
|
}, [comments]);
|
|
}
|
|
|
|
/**
|
|
* Shared delete/update callbacks that handle external-vs-internal branching.
|
|
* Used by both Pierre diff-viewer and Monaco diff-viewer.
|
|
*/
|
|
export function useCommentActions(params: {
|
|
removeComment: (commentId: string) => void;
|
|
updateComment: (commentId: string, updates: DiffCommentUpdate) => void;
|
|
setEditingComment: (id: string | null) => void;
|
|
onCommentDelete?: (commentId: string) => void;
|
|
onCommentUpdate?: (commentId: string, updates: DiffCommentUpdate) => void;
|
|
externalComments?: DiffComment[];
|
|
}) {
|
|
const {
|
|
removeComment,
|
|
updateComment,
|
|
setEditingComment,
|
|
onCommentDelete,
|
|
onCommentUpdate,
|
|
externalComments,
|
|
} = params;
|
|
|
|
const handleCommentDelete = useCallback(
|
|
(commentId: string) => {
|
|
if (externalComments !== undefined) {
|
|
if (onCommentDelete) {
|
|
onCommentDelete(commentId);
|
|
} else if (isDevelopmentMode()) {
|
|
console.warn(
|
|
"[DiffViewer] `comments` is set without `onCommentDelete`; deleted comments must be handled by the controlled owner.",
|
|
);
|
|
}
|
|
} else {
|
|
removeComment(commentId);
|
|
}
|
|
},
|
|
[removeComment, onCommentDelete, externalComments],
|
|
);
|
|
|
|
const handleCommentUpdate = useCallback(
|
|
(commentId: string, content: string) => {
|
|
const updates = { text: content };
|
|
if (externalComments !== undefined) {
|
|
if (onCommentUpdate) {
|
|
onCommentUpdate(commentId, updates);
|
|
setEditingComment(null);
|
|
} else if (isDevelopmentMode()) {
|
|
console.warn(
|
|
"[DiffViewer] `comments` is set without `onCommentUpdate`; edited comments must be handled by the controlled owner.",
|
|
);
|
|
}
|
|
} else {
|
|
updateComment(commentId, updates);
|
|
setEditingComment(null);
|
|
}
|
|
},
|
|
[updateComment, onCommentUpdate, externalComments, setEditingComment],
|
|
);
|
|
|
|
return { handleCommentDelete, handleCommentUpdate };
|
|
}
|
|
|
|
function isDevelopmentMode(): boolean {
|
|
const viteEnv = (
|
|
import.meta as unknown as {
|
|
env?: { DEV?: boolean };
|
|
}
|
|
).env;
|
|
return (
|
|
viteEnv?.DEV === true ||
|
|
(typeof process !== "undefined" && process.env.NODE_ENV !== "production")
|
|
);
|
|
}
|