feat: delete non empty folders
This commit is contained in:
+141
-2
@@ -47,6 +47,9 @@ let renameState = {
|
||||
source: null,
|
||||
name: "",
|
||||
};
|
||||
let deleteConfirmState = {
|
||||
path: null,
|
||||
};
|
||||
let batchMoveState = {
|
||||
destinationBase: "",
|
||||
count: 0,
|
||||
@@ -164,6 +167,15 @@ function setStatus(msg) {
|
||||
}
|
||||
|
||||
function setError(id, msg) {
|
||||
if (id === "actions-error") {
|
||||
document.getElementById(id).textContent = "";
|
||||
if (msg) {
|
||||
openFeedbackModal(msg);
|
||||
} else {
|
||||
closeFeedbackModal();
|
||||
}
|
||||
return;
|
||||
}
|
||||
document.getElementById(id).textContent = msg || "";
|
||||
}
|
||||
|
||||
@@ -281,6 +293,24 @@ function batchMoveElements() {
|
||||
};
|
||||
}
|
||||
|
||||
function deleteConfirmElements() {
|
||||
return {
|
||||
overlay: document.getElementById("delete-confirm-modal"),
|
||||
path: document.getElementById("delete-confirm-path"),
|
||||
error: document.getElementById("delete-confirm-error"),
|
||||
applyButton: document.getElementById("delete-confirm-apply-btn"),
|
||||
cancelButton: document.getElementById("delete-confirm-cancel-btn"),
|
||||
};
|
||||
}
|
||||
|
||||
function feedbackElements() {
|
||||
return {
|
||||
overlay: document.getElementById("feedback-modal"),
|
||||
message: document.getElementById("feedback-message"),
|
||||
closeButton: document.getElementById("feedback-close-btn"),
|
||||
};
|
||||
}
|
||||
|
||||
function settingsElements() {
|
||||
return {
|
||||
overlay: document.getElementById("settings-modal"),
|
||||
@@ -347,6 +377,28 @@ function uploadModalElements() {
|
||||
};
|
||||
}
|
||||
|
||||
function isFeedbackModalOpen() {
|
||||
return !feedbackElements().overlay.classList.contains("hidden");
|
||||
}
|
||||
|
||||
function openFeedbackModal(message) {
|
||||
const elements = feedbackElements();
|
||||
if (!elements.overlay) {
|
||||
return;
|
||||
}
|
||||
elements.message.textContent = message || "";
|
||||
elements.overlay.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function closeFeedbackModal() {
|
||||
const elements = feedbackElements();
|
||||
if (!elements.overlay) {
|
||||
return;
|
||||
}
|
||||
elements.message.textContent = "";
|
||||
elements.overlay.classList.add("hidden");
|
||||
}
|
||||
|
||||
function setUploadModalVisible(visible) {
|
||||
const elements = uploadModalElements();
|
||||
if (!elements.overlay) {
|
||||
@@ -486,8 +538,7 @@ async function apiRequest(method, url, body) {
|
||||
const response = await fetch(url, options);
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
const error = data.error || {};
|
||||
throw new Error(error.message || `HTTP ${response.status}`);
|
||||
throw createApiError(response, data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
@@ -1831,6 +1882,40 @@ async function renameSelected() {
|
||||
}
|
||||
}
|
||||
|
||||
function closeDeleteConfirmModal() {
|
||||
const elements = deleteConfirmElements();
|
||||
deleteConfirmState.path = null;
|
||||
elements.error.textContent = "";
|
||||
elements.overlay.classList.add("hidden");
|
||||
}
|
||||
|
||||
function openDeleteConfirmModal(path) {
|
||||
const elements = deleteConfirmElements();
|
||||
deleteConfirmState.path = path;
|
||||
elements.path.textContent = path;
|
||||
elements.error.textContent = "";
|
||||
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];
|
||||
@@ -1849,6 +1934,16 @@ async function deleteSelected() {
|
||||
await apiRequest("POST", "/api/files/delete", { path: 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;
|
||||
}
|
||||
failures += 1;
|
||||
if (!firstError) {
|
||||
firstError = `${item.path}: ${err.message}`;
|
||||
@@ -2089,6 +2184,10 @@ function isBatchMovePopupOpen() {
|
||||
return !batchMoveElements().overlay.classList.contains("hidden");
|
||||
}
|
||||
|
||||
function isDeleteConfirmModalOpen() {
|
||||
return !deleteConfirmElements().overlay.classList.contains("hidden");
|
||||
}
|
||||
|
||||
function isUploadConflictModalOpen() {
|
||||
return isUploadConflictOpen();
|
||||
}
|
||||
@@ -3150,6 +3249,13 @@ function handleKeyboardShortcuts(event) {
|
||||
closeUploadMenu();
|
||||
return;
|
||||
}
|
||||
if (isFeedbackModalOpen()) {
|
||||
if (event.key === "Escape" || event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
closeFeedbackModal();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isInfoOpen()) {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
@@ -3204,6 +3310,19 @@ function handleKeyboardShortcuts(event) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isDeleteConfirmModalOpen()) {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
closeDeleteConfirmModal();
|
||||
return;
|
||||
}
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
submitDeleteConfirmModal();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (isUploadConflictModalOpen()) {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
@@ -3393,6 +3512,17 @@ function setupEvents() {
|
||||
if (modalCancel) {
|
||||
modalCancel.onclick = requestUploadCancel;
|
||||
}
|
||||
const feedback = feedbackElements();
|
||||
if (feedback.closeButton) {
|
||||
feedback.closeButton.onclick = closeFeedbackModal;
|
||||
}
|
||||
if (feedback.overlay) {
|
||||
feedback.overlay.onclick = (event) => {
|
||||
if (event.target === feedback.overlay) {
|
||||
closeFeedbackModal();
|
||||
}
|
||||
};
|
||||
}
|
||||
document.addEventListener("click", (event) => {
|
||||
const elements = uploadElements();
|
||||
if (!elements.menu || elements.menu.contains(event.target)) {
|
||||
@@ -3514,6 +3644,15 @@ function setupEvents() {
|
||||
}
|
||||
};
|
||||
|
||||
const deleteConfirm = deleteConfirmElements();
|
||||
deleteConfirm.cancelButton.onclick = closeDeleteConfirmModal;
|
||||
deleteConfirm.applyButton.onclick = submitDeleteConfirmModal;
|
||||
deleteConfirm.overlay.onclick = (event) => {
|
||||
if (event.target === deleteConfirm.overlay) {
|
||||
closeDeleteConfirmModal();
|
||||
}
|
||||
};
|
||||
|
||||
const viewer = viewerElements();
|
||||
viewer.closeButton.onclick = closeViewer;
|
||||
viewer.overlay.onclick = (event) => {
|
||||
|
||||
@@ -656,6 +656,10 @@ button:disabled {
|
||||
box-shadow: var(--shadow-elevated);
|
||||
}
|
||||
|
||||
.feedback-card {
|
||||
width: min(440px, calc(100vw - 24px));
|
||||
}
|
||||
|
||||
#upload-modal .popup-card {
|
||||
max-width: 320px;
|
||||
padding: 12px 14px;
|
||||
|
||||
@@ -108,6 +108,16 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="feedback-modal" class="popup-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="feedback-title">
|
||||
<div class="popup-card feedback-card">
|
||||
<h3 id="feedback-title">Action feedback</h3>
|
||||
<div id="feedback-message" class="popup-meta"></div>
|
||||
<div class="popup-actions">
|
||||
<button id="feedback-close-btn" type="button">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings-modal" class="popup-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="settings-title">
|
||||
<div class="popup-card settings-card">
|
||||
<button id="settings-close-btn" class="viewer-close" type="button" aria-label="Close settings">X</button>
|
||||
@@ -257,6 +267,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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-path" class="popup-meta"></div>
|
||||
<div id="delete-confirm-error" class="error"></div>
|
||||
<div class="popup-actions">
|
||||
<button id="delete-confirm-apply-btn" type="button">Delete</button>
|
||||
<button id="delete-confirm-cancel-btn" type="button">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="viewer-modal" class="popup-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="viewer-title">
|
||||
<div class="popup-card viewer-card">
|
||||
<button id="viewer-close-btn" class="viewer-close" type="button" aria-label="Close viewer">X</button>
|
||||
|
||||
Reference in New Issue
Block a user