Consolidate multi-line comment inputs in Plan and File views* (#371)
* refactor: adapt BlockWidget to use shared container map Enable BlockWidget to reuse DOM containers via a shared map Pass container map into widget decorations to preserve portals across updates * feat: enable drag-to-select and guard new comments in FilesView Guard new comment input from opening during drag-to-select actions Enable drag-based line selection with initial drag state and mobile support Reset dragging state on mouseup to end the selection * fix(PlanView): gate new comment input during dragging Add drag state to disable new comment input during selection drag Indicate drag mode during mobile tap-extend to avoid unintended actions Pass drag state to child UI to reflect ongoing drag
This commit is contained in:
@@ -647,6 +647,7 @@ export const FilesView: React.FC = () => {
|
||||
const [lineSelection, setLineSelection] = React.useState<SelectedLineRange | null>(null);
|
||||
const isSelectingRef = React.useRef(false);
|
||||
const selectionStartRef = React.useRef<number | null>(null);
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
|
||||
// Session/config for sending comments
|
||||
const currentSessionId = useSessionStore((state) => state.currentSessionId);
|
||||
@@ -663,6 +664,7 @@ export const FilesView: React.FC = () => {
|
||||
const handleGlobalMouseUp = () => {
|
||||
isSelectingRef.current = false;
|
||||
selectionStartRef.current = null;
|
||||
setIsDragging(false);
|
||||
};
|
||||
document.addEventListener('mouseup', handleGlobalMouseUp);
|
||||
return () => document.removeEventListener('mouseup', handleGlobalMouseUp);
|
||||
@@ -1805,10 +1807,10 @@ export const FilesView: React.FC = () => {
|
||||
});
|
||||
|
||||
// Add input for new comment
|
||||
if (lineSelection && !editingDraftId) {
|
||||
if (lineSelection && !editingDraftId && !isDragging) {
|
||||
widgets.push({
|
||||
afterLine: lineSelection.end,
|
||||
id: 'new-comment-input',
|
||||
id: 'files-new-comment-input',
|
||||
content: (
|
||||
<InlineCommentInput
|
||||
lineRange={lineSelection}
|
||||
@@ -1820,7 +1822,7 @@ export const FilesView: React.FC = () => {
|
||||
}
|
||||
|
||||
return widgets;
|
||||
}, [selectedFile, currentSessionId, allDrafts, editingDraftId, lineSelection, handleSaveComment, removeDraft]);
|
||||
}, [selectedFile, currentSessionId, allDrafts, editingDraftId, lineSelection, handleSaveComment, removeDraft, isDragging]);
|
||||
|
||||
const fileViewer = (
|
||||
<div
|
||||
@@ -2205,19 +2207,21 @@ export const FilesView: React.FC = () => {
|
||||
const lineNumber = view.state.doc.lineAt(line.from).number;
|
||||
|
||||
// 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);
|
||||
setLineSelection({ start, end });
|
||||
isSelectingRef.current = false;
|
||||
selectionStartRef.current = null;
|
||||
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);
|
||||
setLineSelection({ start, end });
|
||||
isSelectingRef.current = false;
|
||||
selectionStartRef.current = null;
|
||||
setIsDragging(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
isSelectingRef.current = true;
|
||||
selectionStartRef.current = lineNumber;
|
||||
isSelectingRef.current = true;
|
||||
selectionStartRef.current = lineNumber;
|
||||
setIsDragging(true);
|
||||
|
||||
if (lineSelection && event.shiftKey) {
|
||||
if (lineSelection && event.shiftKey) {
|
||||
const start = Math.min(lineSelection.start, lineNumber);
|
||||
const end = Math.max(lineSelection.end, lineNumber);
|
||||
setLineSelection({ start, end });
|
||||
@@ -2239,17 +2243,19 @@ export const FilesView: React.FC = () => {
|
||||
}
|
||||
|
||||
const lineNumber = view.state.doc.lineAt(line.from).number;
|
||||
const start = Math.min(selectionStartRef.current, lineNumber);
|
||||
const end = Math.max(selectionStartRef.current, lineNumber);
|
||||
setLineSelection({ start, end });
|
||||
return false;
|
||||
const start = Math.min(selectionStartRef.current, lineNumber);
|
||||
const end = Math.max(selectionStartRef.current, lineNumber);
|
||||
setLineSelection({ start, end });
|
||||
setIsDragging(true);
|
||||
return false;
|
||||
},
|
||||
mouseup: () => {
|
||||
isSelectingRef.current = false;
|
||||
selectionStartRef.current = null;
|
||||
setIsDragging(false);
|
||||
return false;
|
||||
},
|
||||
},
|
||||
mouseup: () => {
|
||||
isSelectingRef.current = false;
|
||||
selectionStartRef.current = null;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -156,11 +156,13 @@ export const PlanView: React.FC = () => {
|
||||
}, []);
|
||||
const isSelectingRef = React.useRef(false);
|
||||
const selectionStartRef = React.useRef<number | null>(null);
|
||||
const [isDragging, setIsDragging] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleGlobalMouseUp = () => {
|
||||
isSelectingRef.current = false;
|
||||
selectionStartRef.current = null;
|
||||
setIsDragging(false);
|
||||
};
|
||||
document.addEventListener('mouseup', handleGlobalMouseUp);
|
||||
return () => document.removeEventListener('mouseup', handleGlobalMouseUp);
|
||||
@@ -428,10 +430,10 @@ export const PlanView: React.FC = () => {
|
||||
});
|
||||
|
||||
// Add new comment input if selecting AND not editing an existing draft
|
||||
if (lineSelection && !editingDraftId) {
|
||||
if (lineSelection && !editingDraftId && !isDragging) {
|
||||
widgets.push({
|
||||
afterLine: lineSelection.end,
|
||||
id: 'new-comment-input',
|
||||
id: 'plan-new-comment-input',
|
||||
content: (
|
||||
<InlineCommentInput
|
||||
fileLabel={fileLabel}
|
||||
@@ -457,6 +459,7 @@ export const PlanView: React.FC = () => {
|
||||
handleSaveComment,
|
||||
handleCancelComment,
|
||||
removeDraft,
|
||||
isDragging,
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -593,11 +596,14 @@ export const PlanView: React.FC = () => {
|
||||
setLineSelection({ start, end });
|
||||
isSelectingRef.current = false;
|
||||
selectionStartRef.current = null;
|
||||
// Mobile tap-extend is atomic, so we don't start drag
|
||||
setIsDragging(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
isSelectingRef.current = true;
|
||||
selectionStartRef.current = lineNumber;
|
||||
setIsDragging(true);
|
||||
|
||||
if (lineSelection && event.shiftKey) {
|
||||
const start = Math.min(lineSelection.start, lineNumber);
|
||||
@@ -614,17 +620,19 @@ export const PlanView: React.FC = () => {
|
||||
if (event.buttons !== 1) return false;
|
||||
if (!isSelectingRef.current || selectionStartRef.current === null) return false;
|
||||
const lineNumber = view.state.doc.lineAt(line.from).number;
|
||||
const start = Math.min(selectionStartRef.current, lineNumber);
|
||||
const end = Math.max(selectionStartRef.current, lineNumber);
|
||||
setLineSelection({ start, end });
|
||||
return false;
|
||||
const start = Math.min(selectionStartRef.current, lineNumber);
|
||||
const end = Math.max(selectionStartRef.current, lineNumber);
|
||||
setLineSelection({ start, end });
|
||||
setIsDragging(true);
|
||||
return false;
|
||||
},
|
||||
mouseup: () => {
|
||||
isSelectingRef.current = false;
|
||||
selectionStartRef.current = null;
|
||||
setIsDragging(false);
|
||||
return false;
|
||||
},
|
||||
},
|
||||
mouseup: () => {
|
||||
isSelectingRef.current = false;
|
||||
selectionStartRef.current = null;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user