fix: resolve remaining trash UI issues and permanent delete backend logic
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m20s

This commit is contained in:
Elijah 2026-05-23 14:20:03 -07:00
parent db946461d7
commit 854da9a50d
2 changed files with 46 additions and 34 deletions

View file

@ -534,11 +534,21 @@ func (h *FSHandler) Delete(c *fiber.Ctx) error {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "file not found"}) return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "file not found"})
} }
// Soft-delete: move to .trash directory
trashPath := filepath.Join(h.Config.TrashDir, relativePath)
// Check if permanent // Check if permanent
isPermanent := c.Query("permanent") == "true" isPermanent := c.Query("permanent") == "true"
if isPermanent { if isPermanent {
if err := os.RemoveAll(resolvedPath); err != nil { // Ensure we don't delete the root
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to permanently delete"}) if resolvedPath == c.Locals("resolvedPath").(string) && relativePath == "." {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "cannot permanently delete root"})
}
// Remove both potential locations to be safe
os.RemoveAll(resolvedPath)
if relativePath != "." {
os.RemoveAll(trashPath)
} }
cleanPath := filepath.ToSlash(relativePath) cleanPath := filepath.ToSlash(relativePath)
@ -547,8 +557,6 @@ func (h *FSHandler) Delete(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"message": "permanently deleted"}) return c.JSON(fiber.Map{"message": "permanently deleted"})
} }
// Soft-delete: move to .trash directory
trashPath := filepath.Join(h.Config.TrashDir, relativePath)
if err := os.MkdirAll(filepath.Dir(trashPath), 0755); err != nil { if err := os.MkdirAll(filepath.Dir(trashPath), 0755); err != nil {
fmt.Printf("MkdirAll error: %v\n", err) fmt.Printf("MkdirAll error: %v\n", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to prepare trash"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to prepare trash"})

View file

@ -772,7 +772,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
} }
function handleDragOver(e: React.DragEvent) { function handleDragOver(e: React.DragEvent) {
if (draggedFile) return; if (draggedFile || activeSection === 'trash') return;
if (!e.dataTransfer.types.includes('Files')) return; if (!e.dataTransfer.types.includes('Files')) return;
e.preventDefault(); e.preventDefault();
setDragOver(true); setDragOver(true);
@ -1754,31 +1754,33 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
</span> </span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button {activeSection !== 'trash' && (
className="p-2 hover:bg-white/5 rounded-lg transition-colors flex items-center gap-2 text-sm" <button
style={{ color: 'var(--color-text-primary)' }} className="p-2 hover:bg-white/5 rounded-lg transition-colors flex items-center gap-2 text-sm"
title="Download Selected" style={{ color: 'var(--color-text-primary)' }}
onClick={() => { title="Download Selected"
if (!downloadToken) return; onClick={() => {
const form = document.createElement('form'); if (!downloadToken) return;
form.method = 'POST'; const form = document.createElement('form');
form.action = api.getBulkDownloadUrl(downloadToken); form.method = 'POST';
form.target = '_blank'; form.action = api.getBulkDownloadUrl(downloadToken);
form.target = '_blank';
const input = document.createElement('input'); const input = document.createElement('input');
input.type = 'hidden'; input.type = 'hidden';
input.name = 'paths'; input.name = 'paths';
input.value = JSON.stringify(Array.from(selectedFiles)); input.value = JSON.stringify(Array.from(selectedFiles));
form.appendChild(input); form.appendChild(input);
document.body.appendChild(form); document.body.appendChild(form);
form.submit(); form.submit();
document.body.removeChild(form); document.body.removeChild(form);
setSelectedFiles(new Set()); setSelectedFiles(new Set());
}} }}
> >
<Download className="w-4 h-4" /> <Download className="w-4 h-4" />
</button> </button>
)}
{activeSection !== 'trash' && ( {activeSection !== 'trash' && (
<button <button
className="p-2 hover:bg-white/5 rounded-lg transition-colors flex items-center gap-2 text-sm" className="p-2 hover:bg-white/5 rounded-lg transition-colors flex items-center gap-2 text-sm"
@ -2094,11 +2096,13 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
style={{ backgroundColor: 'var(--color-bg-primary)', border: '1px solid var(--color-border)' }} style={{ backgroundColor: 'var(--color-bg-primary)', border: '1px solid var(--color-border)' }}
> >
<div className="p-6"> <div className="p-6">
<h2 className="text-xl font-bold mb-2" style={{ color: 'var(--color-text-primary)' }}>Move to Trash?</h2> <h2 className="text-xl font-bold mb-2" style={{ color: 'var(--color-text-primary)' }}>
{activeSection === 'trash' ? 'Delete Permanently?' : 'Move to Trash?'}
</h2>
<p className="mb-6" style={{ color: 'var(--color-text-secondary)' }}> <p className="mb-6" style={{ color: 'var(--color-text-secondary)' }}>
{deleteConfirm.type === 'multiple' {deleteConfirm.type === 'multiple'
? `Are you sure you want to move ${deleteConfirm.count} items to the trash?` ? (activeSection === 'trash' ? `Are you sure you want to permanently delete ${deleteConfirm.count} items?` : `Are you sure you want to move ${deleteConfirm.count} items to the trash?`)
: `Are you sure you want to move ${deleteConfirm.file?.name} to the trash?`} : (activeSection === 'trash' ? `Are you sure you want to permanently delete ${deleteConfirm.file?.name}?` : `Are you sure you want to move ${deleteConfirm.file?.name} to the trash?`)}
</p> </p>
<div className="flex justify-end gap-3"> <div className="flex justify-end gap-3">
<button <button
@ -2115,7 +2119,7 @@ export default function FileExplorer({ onLogout }: FileExplorerProps) {
}} }}
className="px-4 py-2 rounded-lg text-sm font-medium transition-colors hover:opacity-90 bg-red-500 text-white" className="px-4 py-2 rounded-lg text-sm font-medium transition-colors hover:opacity-90 bg-red-500 text-white"
> >
Move to Trash {activeSection === 'trash' ? 'Delete Permanently' : 'Move to Trash'}
</button> </button>
</div> </div>
</div> </div>