diff --git a/src/pages/views/codeeditor/CodeEditor.module.css b/src/pages/views/codeeditor/CodeEditor.module.css index 96ce517..934aaf5 100644 --- a/src/pages/views/codeeditor/CodeEditor.module.css +++ b/src/pages/views/codeeditor/CodeEditor.module.css @@ -74,6 +74,12 @@ color: var(--text-secondary, #666); } +.dragHint { + font-size: 11px; + color: var(--text-secondary, #999); + font-style: italic; +} + .fileItems { flex: 1; overflow-y: auto; @@ -84,8 +90,8 @@ display: flex; align-items: center; gap: 8px; - padding: 8px 16px; - cursor: pointer; + padding: 6px 16px; + cursor: grab; transition: background 0.1s; } @@ -93,13 +99,32 @@ background: var(--hover-bg, #f5f5f5); } -.fileItemSelected { - background: var(--selected-bg, #e8f0fe); +.fileItem:active { + cursor: grabbing; + opacity: 0.7; } -.fileCheckbox { - color: var(--primary-color, #4a90d9); +.dragHandle { + color: var(--text-secondary, #ccc); flex-shrink: 0; + font-size: 10px; +} + +.fileItem:hover .dragHandle { + color: var(--text-secondary, #999); +} + +.dateGroup { + margin-bottom: 4px; +} + +.dateGroupHeader { + padding: 6px 16px 2px; + font-size: 11px; + font-weight: 600; + color: var(--text-secondary, #999); + text-transform: uppercase; + letter-spacing: 0.5px; } .fileIcon { @@ -161,6 +186,12 @@ background: var(--disabled-bg, #f5f5f5); } +.inputDropTarget { + border-color: var(--primary-color, #4a90d9); + background: var(--selected-bg, #e8f0fe); + box-shadow: 0 0 0 2px var(--primary-color, #4a90d9) inset; +} + /* Mode Toggle */ .modeToggle { display: flex; diff --git a/src/pages/views/codeeditor/CodeEditorPage.tsx b/src/pages/views/codeeditor/CodeEditorPage.tsx index fc8d1f0..05c2245 100644 --- a/src/pages/views/codeeditor/CodeEditorPage.tsx +++ b/src/pages/views/codeeditor/CodeEditorPage.tsx @@ -3,7 +3,7 @@ * * Main page for the CodeEditor feature. * Three-panel layout: FileList (left) | Chat (center) | DiffPreview (right) - * Supports simple mode (Phase 1) and agent mode (Phase 2). + * Files are dragged from FileList into the prompt textarea as @fileName references. */ import React, { useState, useRef, useCallback } from 'react'; @@ -22,11 +22,10 @@ export const CodeEditorPage: React.FC = () => { const inputRef = useRef(null); const [inputValue, setInputValue] = useState(''); const [mode, setMode] = useState<'simple' | 'agent'>('simple'); + const [isDragOver, setIsDragOver] = useState(false); const { messages, - selectedFileIds, - toggleFileSelection, pendingEdits, acceptEdit, rejectEdit, @@ -64,9 +63,9 @@ export const CodeEditorPage: React.FC = () => { const handleSubmit = useCallback(() => { const trimmed = inputValue.trim(); if (!trimmed || isProcessing) return; - sendMessage(trimmed, selectedFileIds, mode); + sendMessage(trimmed, mode); setInputValue(''); - }, [inputValue, isProcessing, sendMessage, selectedFileIds, mode]); + }, [inputValue, isProcessing, sendMessage, mode]); const handleKeyDown = useCallback((e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { @@ -75,6 +74,50 @@ export const CodeEditorPage: React.FC = () => { } }, [handleSubmit]); + const handleDragOver = useCallback((e: React.DragEvent) => { + if (e.dataTransfer.types.includes('application/x-codeeditor-file')) { + e.preventDefault(); + e.dataTransfer.dropEffect = 'copy'; + setIsDragOver(true); + } + }, []); + + const handleDragLeave = useCallback(() => { + setIsDragOver(false); + }, []); + + const handleDrop = useCallback((e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + + const fileDataStr = e.dataTransfer.getData('application/x-codeeditor-file'); + if (!fileDataStr) return; + + try { + const fileData = JSON.parse(fileDataStr); + const tag = `@${fileData.fileName}`; + const textarea = inputRef.current; + if (textarea) { + const pos = textarea.selectionStart || inputValue.length; + const before = inputValue.slice(0, pos); + const after = inputValue.slice(pos); + const spaceBefore = before.length > 0 && !before.endsWith(' ') && !before.endsWith('\n') ? ' ' : ''; + const spaceAfter = after.length > 0 && !after.startsWith(' ') && !after.startsWith('\n') ? ' ' : ''; + const newValue = `${before}${spaceBefore}${tag}${spaceAfter}${after}`; + setInputValue(newValue); + requestAnimationFrame(() => { + const newPos = pos + spaceBefore.length + tag.length + spaceAfter.length; + textarea.focus(); + textarea.setSelectionRange(newPos, newPos); + }); + } else { + setInputValue(prev => prev + (prev && !prev.endsWith(' ') ? ' ' : '') + tag + ' '); + } + } catch { + // ignore malformed drop data + } + }, [inputValue]); + return (
{ > {/* Left: File List */}
- +
@@ -116,7 +155,7 @@ export const CodeEditorPage: React.FC = () => { className={`${styles.modeButton} ${mode === 'simple' ? styles.modeActive : ''}`} onClick={() => setMode('simple')} disabled={isProcessing} - title="Single AI call with selected files as context" + title="Single AI call -- drag files into prompt as @references" > Simple @@ -132,21 +171,24 @@ export const CodeEditorPage: React.FC = () => {