fix: preserve inline comment drafts on focus changes
Keeps typed or pasted comment text when the editor remounts Prevents focus changes from dismissing active inline comments Allows cancelling by pressing the selected line number again
This commit is contained in:
@@ -990,9 +990,10 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
});
|
||||
|
||||
const {
|
||||
drafts: filesFileDrafts,
|
||||
commentText,
|
||||
editingDraftId,
|
||||
drafts: filesFileDrafts,
|
||||
commentText,
|
||||
setCommentText,
|
||||
editingDraftId,
|
||||
setSelection: setCommentSelection,
|
||||
saveComment,
|
||||
cancel,
|
||||
@@ -1023,8 +1024,10 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
if (target.closest('.cm-gutterElement')) return;
|
||||
if (target.closest('[data-sonner-toast]') || target.closest('[data-sonner-toaster]')) return;
|
||||
|
||||
setLineSelection(null);
|
||||
cancel();
|
||||
if (!commentText.trim()) {
|
||||
setLineSelection(null);
|
||||
cancel();
|
||||
}
|
||||
};
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
@@ -1035,7 +1038,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
clearTimeout(timeoutId);
|
||||
document.removeEventListener('click', handleClickOutside);
|
||||
};
|
||||
}, [cancel, editingDraftId, lineSelection]);
|
||||
}, [cancel, commentText, editingDraftId, lineSelection]);
|
||||
|
||||
const handleSaveComment = React.useCallback((text: string, range?: { start: number; end: number }) => {
|
||||
const finalRange = range ?? lineSelection ?? undefined;
|
||||
@@ -2964,9 +2967,10 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
const blockWidgets = React.useMemo(() => {
|
||||
return buildCodeMirrorCommentWidgets({
|
||||
drafts: filesFileDrafts,
|
||||
editingDraftId,
|
||||
commentText,
|
||||
selection: lineSelection,
|
||||
editingDraftId,
|
||||
commentText,
|
||||
onTextChange: setCommentText,
|
||||
selection: lineSelection,
|
||||
isDragging,
|
||||
fileLabel: selectedFile?.path ?? '',
|
||||
newWidgetId: 'files-new-comment-input',
|
||||
@@ -2982,7 +2986,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
},
|
||||
onDelete: deleteDraft,
|
||||
});
|
||||
}, [cancel, commentText, deleteDraft, editingDraftId, filesFileDrafts, handleSaveComment, isDragging, lineSelection, selectedFile?.path, startEdit]);
|
||||
}, [cancel, commentText, deleteDraft, editingDraftId, filesFileDrafts, handleSaveComment, isDragging, lineSelection, selectedFile?.path, setCommentText, startEdit]);
|
||||
|
||||
const renderShikiFileView = React.useCallback((file: FileNode, content: string) => {
|
||||
return (
|
||||
@@ -3762,9 +3766,23 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
const lineNumber = view.state.doc.lineAt(line.from).number;
|
||||
|
||||
// Mobile: tap-to-extend selection
|
||||
const lineNumber = view.state.doc.lineAt(line.from).number;
|
||||
|
||||
if (
|
||||
lineSelection &&
|
||||
!event.shiftKey &&
|
||||
Math.min(lineSelection.start, lineSelection.end) === lineNumber &&
|
||||
Math.max(lineSelection.start, lineSelection.end) === lineNumber
|
||||
) {
|
||||
setLineSelection(null);
|
||||
cancel();
|
||||
isSelectingRef.current = false;
|
||||
selectionStartRef.current = null;
|
||||
setIsDragging(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mobile: tap-to-extend selection
|
||||
if (isMobile && lineSelection && !event.shiftKey) {
|
||||
const start = Math.min(lineSelection.start, lineSelection.end, lineNumber);
|
||||
const end = Math.max(lineSelection.start, lineSelection.end, lineNumber);
|
||||
|
||||
@@ -253,6 +253,7 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
|
||||
|
||||
const selectionRef = useRef<SelectedLineRange | null>(null);
|
||||
const editingDraftIdRef = useRef<string | null>(null);
|
||||
const commentTextRef = useRef('');
|
||||
// Use a ref to track if we're currently applying a selection programmatically
|
||||
// to avoid loop with onLineSelected callback
|
||||
const isApplyingSelectionRef = useRef(false);
|
||||
@@ -266,6 +267,10 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
|
||||
editingDraftIdRef.current = editingDraftId;
|
||||
}, [editingDraftId]);
|
||||
|
||||
useEffect(() => {
|
||||
commentTextRef.current = commentText;
|
||||
}, [commentText]);
|
||||
|
||||
const handleSelectionChange = useCallback((range: SelectedLineRange | null) => {
|
||||
// Ignore callbacks while we're programmatically applying selection
|
||||
if (isApplyingSelectionRef.current) {
|
||||
@@ -274,6 +279,10 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
|
||||
|
||||
const prevSelection = selectionRef.current;
|
||||
|
||||
if (!range && prevSelection && commentTextRef.current.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mobile tap-to-extend: if selection exists and new tap is on same side, extend range
|
||||
if (isMobile && prevSelection && range && range.side === prevSelection.side) {
|
||||
const start = Math.min(prevSelection.start, range.start);
|
||||
@@ -715,6 +724,7 @@ export const PierreDiffViewer: React.FC<PierreDiffViewerProps> = ({
|
||||
selection={selection}
|
||||
editingDraftId={editingDraftId}
|
||||
commentText={commentText}
|
||||
onTextChange={setCommentText}
|
||||
fileLabel={(fileName?.split('/').pop()) ?? ''}
|
||||
onSave={handleSaveComment}
|
||||
onCancel={handleCancelComment}
|
||||
|
||||
@@ -277,6 +277,7 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null }) => {
|
||||
const {
|
||||
drafts: planFileDrafts,
|
||||
commentText,
|
||||
setCommentText,
|
||||
editingDraftId,
|
||||
setSelection: setCommentSelection,
|
||||
saveComment,
|
||||
@@ -328,8 +329,10 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null }) => {
|
||||
if (target.closest('.cm-gutterElement')) return;
|
||||
if (target.closest('[data-sonner-toast]') || target.closest('[data-sonner-toaster]')) return;
|
||||
|
||||
setLineSelection(null);
|
||||
cancel();
|
||||
if (!commentText.trim()) {
|
||||
setLineSelection(null);
|
||||
cancel();
|
||||
}
|
||||
};
|
||||
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
@@ -340,7 +343,7 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null }) => {
|
||||
window.clearTimeout(timeoutId);
|
||||
document.removeEventListener('click', handleClickOutside);
|
||||
};
|
||||
}, [cancel, editingDraftId, isMobile, lineSelection]);
|
||||
}, [cancel, commentText, editingDraftId, isMobile, lineSelection]);
|
||||
|
||||
const editorExtensions = React.useMemo(() => {
|
||||
const extensions = [createFlexokiCodeMirrorTheme(currentTheme)];
|
||||
@@ -602,6 +605,7 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null }) => {
|
||||
drafts: planFileDrafts,
|
||||
editingDraftId,
|
||||
commentText,
|
||||
onTextChange: setCommentText,
|
||||
selection: lineSelection,
|
||||
isDragging,
|
||||
fileLabel: planFileLabel,
|
||||
@@ -615,7 +619,7 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null }) => {
|
||||
},
|
||||
onDelete: deleteDraft,
|
||||
});
|
||||
}, [commentText, deleteDraft, editingDraftId, handleCancelComment, handleSaveComment, isDragging, lineSelection, planFileDrafts, planFileLabel, startEdit]);
|
||||
}, [commentText, deleteDraft, editingDraftId, handleCancelComment, handleSaveComment, isDragging, lineSelection, planFileDrafts, planFileLabel, setCommentText, startEdit]);
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full min-h-0 min-w-0 w-full flex-col overflow-hidden bg-background">
|
||||
@@ -811,6 +815,20 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null }) => {
|
||||
event.preventDefault();
|
||||
const lineNumber = view.state.doc.lineAt(line.from).number;
|
||||
|
||||
if (
|
||||
lineSelection &&
|
||||
!event.shiftKey &&
|
||||
Math.min(lineSelection.start, lineSelection.end) === lineNumber &&
|
||||
Math.max(lineSelection.start, lineSelection.end) === lineNumber
|
||||
) {
|
||||
setLineSelection(null);
|
||||
cancel();
|
||||
isSelectingRef.current = false;
|
||||
selectionStartRef.current = null;
|
||||
setIsDragging(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isMobile && lineSelection && !event.shiftKey) {
|
||||
const start = Math.min(lineSelection.start, lineSelection.end, lineNumber);
|
||||
const end = Math.max(lineSelection.start, lineSelection.end, lineNumber);
|
||||
|
||||
Reference in New Issue
Block a user