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:
viper151
2025-07-14 17:27:02 +02:00
committed by GitHub
parent 36d0add224
commit f28dc0140e
2 changed files with 92 additions and 3 deletions

View File

@@ -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;

View File

@@ -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 () => {
if (!confirmAction) return;
@@ -305,6 +333,9 @@ function GitPanel({ selectedProject, isMobile }) {
case 'discard':
await discardChanges(file);
break;
case 'delete':
await deleteUntrackedFile(file);
break;
case 'commit':
await handleCommit();
break;
@@ -578,6 +609,23 @@ function GitPanel({ selectedProject, isMobile }) {
{isMobile && <span>Discard</span>}
</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
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' :
@@ -1120,14 +1168,15 @@ function GitPanel({ selectedProject, isMobile }) {
<div className="p-6">
<div className="flex items-center mb-4">
<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 ${
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>
<h3 className="text-lg font-semibold">
{confirmAction.type === 'discard' ? 'Discard Changes' :
confirmAction.type === 'delete' ? 'Delete File' :
confirmAction.type === 'commit' ? 'Confirm Commit' :
confirmAction.type === 'pull' ? 'Confirm Pull' : 'Confirm Push'}
</h3>
@@ -1147,7 +1196,7 @@ function GitPanel({ selectedProject, isMobile }) {
<button
onClick={confirmAndExecute}
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'
: confirmAction.type === 'commit'
? 'bg-blue-600 hover:bg-blue-700'
@@ -1161,6 +1210,11 @@ function GitPanel({ selectedProject, isMobile }) {
<Trash2 className="w-4 h-4" />
<span>Discard</span>
</>
) : confirmAction.type === 'delete' ? (
<>
<Trash2 className="w-4 h-4" />
<span>Delete</span>
</>
) : confirmAction.type === 'commit' ? (
<>
<Check className="w-4 h-4" />