Skip to content
Merged
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions packages/review-editor/components/AllFilesDiffView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export const AllFilesDiffView: React.FC<AllFilesDiffViewProps> = ({
setActiveFilePath(null);
setCollapsedFiles(new Set());
collapseHistory.current = [];
setFileCommentAnchor(null);
}, [files]);

const scrollRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -515,9 +516,11 @@ export const AllFilesDiffView: React.FC<AllFilesDiffViewProps> = ({

{fileCommentAnchor && onAddFileComment && (
<CommentPopover
key={`file:${prUrl ?? ''}:${prDiffScope ?? ''}:${fileCommentAnchor.filePath}`}
anchorEl={fileCommentAnchor.el}
contextText={fileCommentAnchor.filePath.split('/').pop() || fileCommentAnchor.filePath}
isGlobal={false}
draftKey={`file:${prUrl ?? ''}:${prDiffScope ?? ''}:${fileCommentAnchor.filePath}`}
onSubmit={(text) => {
onAddFileComment(fileCommentAnchor.filePath, text);
setFileCommentAnchor(null);
Expand Down
6 changes: 6 additions & 0 deletions packages/review-editor/components/DiffViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ interface DiffViewerProps {
oldPath?: string;
/** Base branch override used for file-content lookups (branch / merge-base modes only). */
reviewBase?: string;
/** Current PR url + diff scope — used to namespace file-comment drafts so they don't leak across in-place PR switches. */
prUrl?: string;
prDiffScope?: string;
isFocused?: boolean;
diffStyle: 'split' | 'unified';
diffOverflow?: 'scroll' | 'wrap';
Expand Down Expand Up @@ -165,6 +168,8 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
filePath,
oldPath,
reviewBase,
prUrl,
prDiffScope,
isFocused = false,
diffStyle,
diffOverflow,
Expand Down Expand Up @@ -621,6 +626,7 @@ export const DiffViewer: React.FC<DiffViewerProps> = ({
anchorEl={fileCommentAnchor}
contextText={filePath.split('/').pop() || filePath}
isGlobal={false}
draftKey={`file:${prUrl ?? ''}:${prDiffScope ?? ''}:${filePath}`}
onSubmit={(text) => {
onAddFileComment(text);
setFileCommentAnchor(null);
Expand Down
2 changes: 2 additions & 0 deletions packages/review-editor/dock/panels/ReviewDiffPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export const ReviewDiffPanel: React.FC<IDockviewPanelProps> = (props) => {
filePath={file.path}
oldPath={file.oldPath}
reviewBase={state.reviewBase}
prUrl={state.prMetadata?.url}
prDiffScope={state.prDiffScope}
isFocused={isFocusedFile}
diffStyle={state.diffStyle}
diffOverflow={state.diffOverflow}
Expand Down
28 changes: 25 additions & 3 deletions packages/ui/components/CommentPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,28 @@ interface CommentPopoverProps {
onSubmit: (text: string, images?: ImageAttachment[]) => void;
/** Called when popover is closed/cancelled */
onClose: () => void;
/** Opt-in: persist text + images across close/reopen, keyed by this string. Cleared on submit. */
draftKey?: string;
}

const MAX_POPOVER_WIDTH = 384;
const GAP = 8;

// Module-level draft store: survives popover unmount so reopening the same key restores in-progress text.
const draftStore = new Map<string, { text: string; images: ImageAttachment[] }>();

/** Mirrors the latest text + images into `draftStore[draftKey]` so they outlive popover unmount. No-op without a key. */
function useCommentDraftSync(draftKey: string | undefined, text: string, images: ImageAttachment[]) {
useEffect(() => {
if (!draftKey) return;
if (text.trim() || images.length > 0) {
draftStore.set(draftKey, { text, images });
} else {
draftStore.delete(draftKey);
}
}, [draftKey, text, images]);
}

function computePosition(anchorRect: DOMRect): { top: number; left: number; flipAbove: boolean; width: number } {
const spaceBelow = window.innerHeight - anchorRect.bottom;
const flipAbove = spaceBelow < 280;
Expand All @@ -48,15 +65,19 @@ export const CommentPopover: React.FC<CommentPopoverProps> = ({
initialText = '',
onSubmit,
onClose,
draftKey,
}) => {
const [mode, setMode] = useState<'popover' | 'dialog'>('popover');
const [text, setText] = useState(initialText);
const [images, setImages] = useState<ImageAttachment[]>([]);
const initialDraft = draftKey ? draftStore.get(draftKey) : undefined;
const [text, setText] = useState(initialDraft?.text ?? initialText);
const [images, setImages] = useState<ImageAttachment[]>(initialDraft?.images ?? []);
const [position, setPosition] = useState<{ top: number; left: number; flipAbove: boolean; width: number } | null>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const popoverRef = useRef<HTMLDivElement>(null);
const { dragPosition, dragHandleProps, wasDragged, reset: resetDrag } = useDraggable(popoverRef);

useCommentDraftSync(draftKey, text, images);

// Reset drag when anchor changes (new annotation) or mode switches
useEffect(() => { resetDrag(); }, [anchorEl, anchorRect, resetDrag]);
useEffect(() => { if (mode === 'popover') resetDrag(); }, [mode, resetDrag]);
Expand Down Expand Up @@ -111,9 +132,10 @@ export const CommentPopover: React.FC<CommentPopoverProps> = ({

const handleSubmit = useCallback(() => {
if (text.trim() || images.length > 0) {
if (draftKey) draftStore.delete(draftKey);
onSubmit(text, images.length > 0 ? images : undefined);
}
}, [text, images, onSubmit]);
}, [text, images, onSubmit, draftKey]);

const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Escape') {
Expand Down
Loading