mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-13 13:49:43 +00:00
Add delete functionality for untracked files (#65)
- Add delete button for untracked files in GitPanel - Implement deleteUntrackedFile function with confirmation dialog - Add /delete-untracked API endpoint to safely delete untracked files - Update confirmation modal to handle delete actions - Maintain consistent UI patterns with existing discard functionality
This commit is contained in:
@@ -692,4 +692,39 @@ router.post('/discard', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Delete untracked file
|
||||||
|
router.post('/delete-untracked', async (req, res) => {
|
||||||
|
const { project, file } = req.body;
|
||||||
|
|
||||||
|
if (!project || !file) {
|
||||||
|
return res.status(400).json({ error: 'Project name and file path are required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const projectPath = await getActualProjectPath(project);
|
||||||
|
await validateGitRepository(projectPath);
|
||||||
|
|
||||||
|
// Check if file is actually untracked
|
||||||
|
const { stdout: statusOutput } = await execAsync(`git status --porcelain "${file}"`, { cwd: projectPath });
|
||||||
|
|
||||||
|
if (!statusOutput.trim()) {
|
||||||
|
return res.status(400).json({ error: 'File is not untracked or does not exist' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = statusOutput.substring(0, 2);
|
||||||
|
|
||||||
|
if (status !== '??') {
|
||||||
|
return res.status(400).json({ error: 'File is not untracked. Use discard for tracked files.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the untracked file
|
||||||
|
await fs.unlink(path.join(projectPath, file));
|
||||||
|
|
||||||
|
res.json({ success: true, message: `Untracked file ${file} deleted successfully` });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Git delete untracked error:', error);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@@ -294,6 +294,34 @@ function GitPanel({ selectedProject, isMobile }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteUntrackedFile = async (filePath) => {
|
||||||
|
try {
|
||||||
|
const response = await authenticatedFetch('/api/git/delete-untracked', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
project: selectedProject.name,
|
||||||
|
file: filePath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.success) {
|
||||||
|
// Remove from selected files and refresh status
|
||||||
|
setSelectedFiles(prev => {
|
||||||
|
const newSet = new Set(prev);
|
||||||
|
newSet.delete(filePath);
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
fetchGitStatus();
|
||||||
|
} else {
|
||||||
|
console.error('Delete failed:', data.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting untracked file:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const confirmAndExecute = async () => {
|
const confirmAndExecute = async () => {
|
||||||
if (!confirmAction) return;
|
if (!confirmAction) return;
|
||||||
|
|
||||||
@@ -305,6 +333,9 @@ function GitPanel({ selectedProject, isMobile }) {
|
|||||||
case 'discard':
|
case 'discard':
|
||||||
await discardChanges(file);
|
await discardChanges(file);
|
||||||
break;
|
break;
|
||||||
|
case 'delete':
|
||||||
|
await deleteUntrackedFile(file);
|
||||||
|
break;
|
||||||
case 'commit':
|
case 'commit':
|
||||||
await handleCommit();
|
await handleCommit();
|
||||||
break;
|
break;
|
||||||
@@ -578,6 +609,23 @@ function GitPanel({ selectedProject, isMobile }) {
|
|||||||
{isMobile && <span>Discard</span>}
|
{isMobile && <span>Discard</span>}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{status === 'U' && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setConfirmAction({
|
||||||
|
type: 'delete',
|
||||||
|
file: filePath,
|
||||||
|
message: `Delete untracked file "${filePath}"? This action cannot be undone.`
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className={`${isMobile ? 'px-2 py-1 text-xs' : 'p-1'} hover:bg-red-100 dark:hover:bg-red-900 rounded text-red-600 dark:text-red-400 font-medium flex items-center gap-1`}
|
||||||
|
title="Delete untracked file"
|
||||||
|
>
|
||||||
|
<Trash2 className={`${isMobile ? 'w-3 h-3' : 'w-3 h-3'}`} />
|
||||||
|
{isMobile && <span>Delete</span>}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center justify-center w-5 h-5 rounded text-xs font-bold border ${
|
className={`inline-flex items-center justify-center w-5 h-5 rounded text-xs font-bold border ${
|
||||||
status === 'M' ? 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300 border-yellow-200 dark:border-yellow-800' :
|
status === 'M' ? 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300 border-yellow-200 dark:border-yellow-800' :
|
||||||
@@ -1120,14 +1168,15 @@ function GitPanel({ selectedProject, isMobile }) {
|
|||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-center mb-4">
|
<div className="flex items-center mb-4">
|
||||||
<div className={`p-2 rounded-full mr-3 ${
|
<div className={`p-2 rounded-full mr-3 ${
|
||||||
confirmAction.type === 'discard' ? 'bg-red-100 dark:bg-red-900' : 'bg-yellow-100 dark:bg-yellow-900'
|
(confirmAction.type === 'discard' || confirmAction.type === 'delete') ? 'bg-red-100 dark:bg-red-900' : 'bg-yellow-100 dark:bg-yellow-900'
|
||||||
}`}>
|
}`}>
|
||||||
<AlertTriangle className={`w-5 h-5 ${
|
<AlertTriangle className={`w-5 h-5 ${
|
||||||
confirmAction.type === 'discard' ? 'text-red-600 dark:text-red-400' : 'text-yellow-600 dark:text-yellow-400'
|
(confirmAction.type === 'discard' || confirmAction.type === 'delete') ? 'text-red-600 dark:text-red-400' : 'text-yellow-600 dark:text-yellow-400'
|
||||||
}`} />
|
}`} />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-semibold">
|
<h3 className="text-lg font-semibold">
|
||||||
{confirmAction.type === 'discard' ? 'Discard Changes' :
|
{confirmAction.type === 'discard' ? 'Discard Changes' :
|
||||||
|
confirmAction.type === 'delete' ? 'Delete File' :
|
||||||
confirmAction.type === 'commit' ? 'Confirm Commit' :
|
confirmAction.type === 'commit' ? 'Confirm Commit' :
|
||||||
confirmAction.type === 'pull' ? 'Confirm Pull' : 'Confirm Push'}
|
confirmAction.type === 'pull' ? 'Confirm Pull' : 'Confirm Push'}
|
||||||
</h3>
|
</h3>
|
||||||
@@ -1147,7 +1196,7 @@ function GitPanel({ selectedProject, isMobile }) {
|
|||||||
<button
|
<button
|
||||||
onClick={confirmAndExecute}
|
onClick={confirmAndExecute}
|
||||||
className={`px-4 py-2 text-sm text-white rounded-md ${
|
className={`px-4 py-2 text-sm text-white rounded-md ${
|
||||||
confirmAction.type === 'discard'
|
(confirmAction.type === 'discard' || confirmAction.type === 'delete')
|
||||||
? 'bg-red-600 hover:bg-red-700'
|
? 'bg-red-600 hover:bg-red-700'
|
||||||
: confirmAction.type === 'commit'
|
: confirmAction.type === 'commit'
|
||||||
? 'bg-blue-600 hover:bg-blue-700'
|
? 'bg-blue-600 hover:bg-blue-700'
|
||||||
@@ -1161,6 +1210,11 @@ function GitPanel({ selectedProject, isMobile }) {
|
|||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-4 h-4" />
|
||||||
<span>Discard</span>
|
<span>Discard</span>
|
||||||
</>
|
</>
|
||||||
|
) : confirmAction.type === 'delete' ? (
|
||||||
|
<>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
<span>Delete</span>
|
||||||
|
</>
|
||||||
) : confirmAction.type === 'commit' ? (
|
) : confirmAction.type === 'commit' ? (
|
||||||
<>
|
<>
|
||||||
<Check className="w-4 h-4" />
|
<Check className="w-4 h-4" />
|
||||||
|
|||||||
Reference in New Issue
Block a user