Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 93 additions & 107 deletions apps/web/src/components/diffs/AnnotatableCodeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
} from "@pierre/diffs";
import type { CodeViewHandle } from "@pierre/diffs/react";
import type { ScopedThreadRef } from "@t3tools/contracts";
import { useCallback, useMemo, useState, type ReactNode, type Ref } from "react";
import { useState, type ReactNode, type Ref } from "react";

import { type DraftId, useComposerDraftStore } from "~/composerDraftStore";
import { fnv1a32, resolveFileDiffPath } from "~/lib/diffRendering";
Expand Down Expand Up @@ -133,119 +133,105 @@ export function AnnotatableCodeView({
} | null>(null);
const [draftText, setDraftText] = useState("");

const filesByKey = useMemo(() => new Map(files.map((file) => [file.fileKey, file])), [files]);
const items = useMemo<CodeViewDiffItem<DiffCommentAnnotationGroup>[]>(
() =>
files.map(({ fileDiff, filePath, fileKey, fileVersion, collapsed }) => {
const persisted = reviewComments
.filter(
(comment) =>
comment.sectionId === sectionId &&
comment.filePath === filePath &&
(comment.fenceLanguage ?? "diff") === "diff",
)
.reduce<DiffCommentLineAnnotation[]>((annotations, comment) => {
const range = restoreDiffReviewCommentRange(fileDiff, comment);
if (!range) return annotations;
return appendAnnotationEntry(annotations, range, {
id: comment.id,
kind: "comment",
range,
rangeLabel: comment.rangeLabel,
text: comment.text,
});
}, []);
const annotations =
draft?.fileKey === fileKey ? [...persisted, draft.annotation] : persisted;
return {
id: fileKey,
type: "diff",
fileDiff,
annotations,
collapsed,
version: fnv1a32(
`${fileVersion}:${collapsed ? "1" : "0"}:${annotations
.flatMap((annotation) =>
annotation.metadata.entries.map(
(entry) => `${entry.id}:${entry.rangeLabel}:${entry.text}`,
),
)
.join(":")}`,
),
};
}),
[draft, files, reviewComments, sectionId],
);

const removeEntry = useCallback(
(entryId: string) => {
setSelectedLines(null);
if (draft?.annotation.metadata.entries.some((entry) => entry.id === entryId)) {
setDraft(null);
setDraftText("");
} else {
removeReviewComment(composerDraftTarget, entryId);
}
const filesByKey = new Map(files.map((file) => [file.fileKey, file]));
const items: CodeViewDiffItem<DiffCommentAnnotationGroup>[] = files.map(
({ fileDiff, filePath, fileKey, fileVersion, collapsed }) => {
const persisted = reviewComments
.filter(
(comment) =>
comment.sectionId === sectionId &&
comment.filePath === filePath &&
(comment.fenceLanguage ?? "diff") === "diff",
)
.reduce<DiffCommentLineAnnotation[]>((annotations, comment) => {
const range = restoreDiffReviewCommentRange(fileDiff, comment);
if (!range) return annotations;
return appendAnnotationEntry(annotations, range, {
id: comment.id,
kind: "comment",
range,
rangeLabel: comment.rangeLabel,
text: comment.text,
});
}, []);
const annotations = draft?.fileKey === fileKey ? [...persisted, draft.annotation] : persisted;
return {
id: fileKey,
type: "diff",
fileDiff,
annotations,
collapsed,
version: fnv1a32(
`${fileVersion}:${collapsed ? "1" : "0"}:${annotations
.flatMap((annotation) =>
annotation.metadata.entries.map(
(entry) => `${entry.id}:${entry.rangeLabel}:${entry.text}`,
),
)
.join(":")}`,
),
};
},
[composerDraftTarget, draft, removeReviewComment],
);

const submitEntry = useCallback(
(entryId: string, text: string) => {
const entry = draft?.annotation.metadata.entries.find(
(candidate) => candidate.id === entryId,
);
const file = draft ? filesByKey.get(draft.fileKey) : undefined;
if (!entry || !file) return;
const comment = buildDiffReviewComment({
id: entry.id,
sectionId,
sectionTitle,
filePath: file.filePath,
fileDiff: file.fileDiff,
range: entry.range,
text,
});
if (comment) addReviewComment(composerDraftTarget, comment);
setSelectedLines(null);
const removeEntry = (entryId: string) => {
setSelectedLines(null);
if (draft?.annotation.metadata.entries.some((entry) => entry.id === entryId)) {
setDraft(null);
setDraftText("");
},
[addReviewComment, composerDraftTarget, draft, filesByKey, sectionId, sectionTitle],
);
} else {
removeReviewComment(composerDraftTarget, entryId);
}
};

const beginComment = useCallback(
(range: SelectedLineRange | null, context: DiffSelectionContext) => {
if (!range) return;
const item = context.item;
if (item.type !== "diff") return;
// Read from the item, not the file list, so this callback keeps its identity as
// patches arrive; the viewer re-applies its options whenever it changes.
const id = nextFileCommentId();
const comment = buildDiffReviewComment({
id,
sectionId,
sectionTitle,
filePath: resolveFileDiffPath(item.fileDiff),
fileDiff: item.fileDiff,
range,
text: "",
});
if (!comment) return;
setDraftText("");
setDraft({
fileKey: item.id,
annotation: {
side: annotationSide(range),
lineNumber: range.end,
metadata: {
entries: [{ id, kind: "draft", range, rangeLabel: comment.rangeLabel, text: "" }],
},
const submitEntry = (entryId: string, text: string) => {
const entry = draft?.annotation.metadata.entries.find((candidate) => candidate.id === entryId);
const file = draft ? filesByKey.get(draft.fileKey) : undefined;
if (!entry || !file) return;
const comment = buildDiffReviewComment({
id: entry.id,
sectionId,
sectionTitle,
filePath: file.filePath,
fileDiff: file.fileDiff,
range: entry.range,
text,
});
if (comment) addReviewComment(composerDraftTarget, comment);
setSelectedLines(null);
setDraft(null);
setDraftText("");
};

const beginComment = (range: SelectedLineRange | null, context: DiffSelectionContext) => {
if (!range) return;
const item = context.item;
if (item.type !== "diff") return;
// Read from the item, not the file list, so the compiled callback keeps its identity
// as patches arrive; the viewer re-applies its options whenever it changes.
const id = nextFileCommentId();
const comment = buildDiffReviewComment({
id,
sectionId,
sectionTitle,
filePath: resolveFileDiffPath(item.fileDiff),
fileDiff: item.fileDiff,
range,
text: "",
});
if (!comment) return;
setDraftText("");
setDraft({
fileKey: item.id,
annotation: {
side: annotationSide(range),
lineNumber: range.end,
metadata: {
entries: [{ id, kind: "draft", range, rangeLabel: comment.rangeLabel, text: "" }],
},
});
},
[sectionId, sectionTitle],
);
},
});
};

const hasOpenComment = draft !== null;
return (
Expand Down
Loading