feat: delete multiple non empty folders

This commit is contained in:
kodi
2026-03-14 08:36:47 +01:00
parent d84b3da561
commit 3987de27e0
6 changed files with 106 additions and 52 deletions
+95 -45
View File
@@ -48,7 +48,9 @@ let renameState = {
name: "",
};
let deleteConfirmState = {
path: null,
pane: "left",
items: [],
recursivePaths: [],
};
let batchMoveState = {
destinationBase: "",
@@ -296,6 +298,8 @@ function batchMoveElements() {
function deleteConfirmElements() {
return {
overlay: document.getElementById("delete-confirm-modal"),
title: document.getElementById("delete-confirm-title"),
message: document.getElementById("delete-confirm-message"),
path: document.getElementById("delete-confirm-path"),
error: document.getElementById("delete-confirm-error"),
applyButton: document.getElementById("delete-confirm-apply-btn"),
@@ -1884,65 +1888,54 @@ async function renameSelected() {
function closeDeleteConfirmModal() {
const elements = deleteConfirmElements();
deleteConfirmState.path = null;
deleteConfirmState.pane = "left";
deleteConfirmState.items = [];
deleteConfirmState.recursivePaths = [];
elements.error.textContent = "";
elements.overlay.classList.add("hidden");
}
function openDeleteConfirmModal(path) {
function openDeleteConfirmModal(pane, items, recursivePaths) {
const elements = deleteConfirmElements();
deleteConfirmState.path = path;
elements.path.textContent = path;
deleteConfirmState.pane = pane;
deleteConfirmState.items = items.map((item) => ({ ...item }));
deleteConfirmState.recursivePaths = Array.from(recursivePaths);
elements.error.textContent = "";
if (items.length === 1) {
elements.title.textContent = "Delete folder and contents?";
elements.message.textContent = "This will permanently delete the folder and all files and subfolders inside it.";
elements.path.textContent = items[0].path;
} else {
elements.title.textContent = "Delete selected items and folder contents?";
elements.message.textContent = `This will permanently delete ${items.length} selected items, including all files and subfolders inside the selected folders.`;
elements.path.textContent = `${items.length} selected items`;
}
elements.overlay.classList.remove("hidden");
}
async function submitDeleteConfirmModal() {
const path = deleteConfirmState.path;
if (!path) {
return;
}
const elements = deleteConfirmElements();
elements.error.textContent = "";
try {
await apiRequest("POST", "/api/files/delete", { path, recursive: true });
closeDeleteConfirmModal();
setSelectedItem(state.activePane, null);
await loadBrowsePane(state.activePane);
setStatus("Delete: 1 success, 0 failed");
setError("actions-error", "");
} catch (err) {
elements.error.textContent = err.message;
}
}
async function deleteSelected() {
const pane = state.activePane;
const selectedItems = [...paneState(pane).selectedItems];
if (selectedItems.length === 0) {
return;
}
if (!window.confirm(`Delete ${selectedItems.length} selected item(s)?`)) {
return;
}
setError("actions-error", "");
async function executeDeleteItems(pane, items, recursivePaths) {
let successes = 0;
let failures = 0;
let firstError = null;
for (const item of selectedItems) {
for (const item of items) {
try {
await apiRequest("POST", "/api/files/delete", { path: item.path });
await apiRequest("POST", "/api/files/delete", {
path: item.path,
recursive: recursivePaths.has(item.path),
});
successes += 1;
} catch (err) {
if (
err.code === "directory_not_empty"
&& selectedItems.length === 1
&& item.kind === "directory"
) {
failures = 0;
firstError = null;
openDeleteConfirmModal(item.path);
return;
if (err.code === "directory_not_empty" && recursivePaths.has(item.path)) {
try {
await apiRequest("POST", "/api/files/delete", {
path: item.path,
recursive: true,
});
successes += 1;
continue;
} catch (retryErr) {
err = retryErr;
}
}
failures += 1;
if (!firstError) {
@@ -1955,6 +1948,63 @@ async function deleteSelected() {
showActionSummary("Delete", successes, failures, firstError);
}
async function submitDeleteConfirmModal() {
const elements = deleteConfirmElements();
if (!deleteConfirmState.items.length) {
return;
}
elements.error.textContent = "";
try {
const pane = deleteConfirmState.pane;
const items = [...deleteConfirmState.items];
const recursivePaths = new Set(deleteConfirmState.recursivePaths);
closeDeleteConfirmModal();
await executeDeleteItems(pane, items, recursivePaths);
} catch (err) {
elements.error.textContent = err.message;
}
}
async function collectDeleteRecursivePaths(selectedItems) {
const recursivePaths = new Set();
for (const item of selectedItems) {
if (item.kind !== "directory") {
continue;
}
const query = new URLSearchParams({
path: item.path,
show_hidden: "true",
});
const data = await apiRequest("GET", `/api/browse?${query.toString()}`);
if ((data.directories && data.directories.length > 0) || (data.files && data.files.length > 0)) {
recursivePaths.add(item.path);
}
}
return recursivePaths;
}
async function deleteSelected() {
const pane = state.activePane;
const selectedItems = [...paneState(pane).selectedItems];
if (selectedItems.length === 0) {
return;
}
setError("actions-error", "");
try {
const recursivePaths = await collectDeleteRecursivePaths(selectedItems);
if (recursivePaths.size > 0) {
openDeleteConfirmModal(pane, selectedItems, recursivePaths);
return;
}
if (!window.confirm(`Delete ${selectedItems.length} selected item(s)?`)) {
return;
}
await executeDeleteItems(pane, selectedItems, new Set());
} catch (err) {
setActionError("Delete", err);
}
}
function defaultDestination(sourcePath, targetBasePath) {
const sourceName = baseName(sourcePath);
return `${targetBasePath}/${sourceName}`;
+2 -1
View File
@@ -565,7 +565,8 @@ button:disabled {
justify-content: center;
gap: 5px;
width: 100%;
max-width: 760px;
max-width: none;
flex-wrap: nowrap;
}
#function-bar button {
+1 -1
View File
@@ -270,7 +270,7 @@
<div id="delete-confirm-modal" class="popup-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="delete-confirm-title">
<div class="popup-card">
<h3 id="delete-confirm-title">Delete folder and contents?</h3>
<div class="popup-meta">This will permanently delete the folder and all files and subfolders inside it.</div>
<div id="delete-confirm-message" class="popup-meta">This will permanently delete the folder and all files and subfolders inside it.</div>
<div id="delete-confirm-path" class="popup-meta"></div>
<div id="delete-confirm-error" class="error"></div>
<div class="popup-actions">