From 2c79ea87058159e4dc41ae5c24146c960531d371 Mon Sep 17 00:00:00 2001 From: Brendan O'Leary Date: Tue, 16 Dec 2025 20:56:33 -0500 Subject: [PATCH] Add delete key --- src/CrossPointWebServer.cpp | 222 +++++++++++++++++++++++++++++++++++- src/CrossPointWebServer.h | 1 + 2 files changed, 220 insertions(+), 3 deletions(-) diff --git a/src/CrossPointWebServer.cpp b/src/CrossPointWebServer.cpp index 62479ac..33b0a75 100644 --- a/src/CrossPointWebServer.cpp +++ b/src/CrossPointWebServer.cpp @@ -511,6 +511,63 @@ static const char* FILES_PAGE_HEADER = R"rawliteral( .folder-btn:hover { background-color: #d68910; } + /* Delete button styles */ + .delete-btn { + background: none; + border: none; + cursor: pointer; + font-size: 1.1em; + padding: 4px 8px; + border-radius: 4px; + color: #95a5a6; + transition: all 0.15s; + } + .delete-btn:hover { + background-color: #fee; + color: #e74c3c; + } + .actions-col { + width: 60px; + text-align: center; + } + /* Delete modal */ + .delete-warning { + color: #e74c3c; + font-weight: 600; + margin: 10px 0; + } + .delete-item-name { + font-weight: 600; + color: #2c3e50; + word-break: break-all; + } + .delete-btn-confirm { + background-color: #e74c3c; + color: white; + padding: 10px 20px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 1em; + width: 100%; + } + .delete-btn-confirm:hover { + background-color: #c0392b; + } + .delete-btn-cancel { + background-color: #95a5a6; + color: white; + padding: 10px 20px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 1em; + width: 100%; + margin-top: 10px; + } + .delete-btn-cancel:hover { + background-color: #7f8c8d; + } @@ -557,6 +614,23 @@ static const char* FILES_PAGE_FOOTER = R"rawliteral( + + + @@ -769,6 +883,9 @@ void CrossPointWebServer::begin() { // Create folder endpoint server->on("/mkdir", HTTP_POST, [this]() { handleCreateFolder(); }); + // Delete file/folder endpoint + server->on("/delete", HTTP_POST, [this]() { handleDelete(); }); + server->onNotFound([this]() { handleNotFound(); }); server->begin(); @@ -1023,7 +1140,7 @@ void CrossPointWebServer::handleFileList() { html += "
This folder is empty
"; } else { html += ""; - html += ""; + html += ""; // Sort files: folders first, then epub files, then other files, alphabetically within each group std::sort(files.begin(), files.end(), [](const FileInfo& a, const FileInfo& b) { @@ -1058,9 +1175,15 @@ void CrossPointWebServer::handleFileList() { html += ""; html += ""; + html += "" + escapeHtml(file.name) + "" + badge + ""; html += ""; html += ""; + // Escape quotes for JavaScript string + String escapedName = file.name; + escapedName.replace("'", "\\'"); + String escapedPath = folderPath; + escapedPath.replace("'", "\\'"); + html += ""; html += ""; } else { rowClass = file.isEpub ? "epub-file" : ""; @@ -1071,10 +1194,21 @@ void CrossPointWebServer::handleFileList() { typeStr = ext; sizeStr = formatFileSize(file.size); + // Build file path for delete + String filePath = currentPath; + if (!filePath.endsWith("/")) filePath += "/"; + filePath += file.name; + html += ""; - html += ""; + html += ""; html += ""; html += ""; + // Escape quotes for JavaScript string + String escapedName = file.name; + escapedName.replace("'", "\\'"); + String escapedPath = filePath; + escapedPath.replace("'", "\\'"); + html += ""; html += ""; } } @@ -1246,3 +1380,85 @@ void CrossPointWebServer::handleCreateFolder() { server->send(500, "text/plain", "Failed to create folder"); } } + +void CrossPointWebServer::handleDelete() { + // Get path from form data + if (!server->hasArg("path")) { + server->send(400, "text/plain", "Missing path"); + return; + } + + String itemPath = server->arg("path"); + String itemType = server->hasArg("type") ? server->arg("type") : "file"; + + // Validate path + if (itemPath.isEmpty() || itemPath == "/") { + server->send(400, "text/plain", "Cannot delete root directory"); + return; + } + + // Ensure path starts with / + if (!itemPath.startsWith("/")) { + itemPath = "/" + itemPath; + } + + // Security check: prevent deletion of protected items + String itemName = itemPath.substring(itemPath.lastIndexOf('/') + 1); + + // Check if item starts with a dot (hidden/system file) + if (itemName.startsWith(".")) { + Serial.printf("[%lu] [WEB] Delete rejected - hidden/system item: %s\n", millis(), itemPath.c_str()); + server->send(403, "text/plain", "Cannot delete system files"); + return; + } + + // Check against explicitly protected items + for (size_t i = 0; i < HIDDEN_ITEMS_COUNT; i++) { + if (itemName.equals(HIDDEN_ITEMS[i])) { + Serial.printf("[%lu] [WEB] Delete rejected - protected item: %s\n", millis(), itemPath.c_str()); + server->send(403, "text/plain", "Cannot delete protected items"); + return; + } + } + + // Check if item exists + if (!SD.exists(itemPath.c_str())) { + Serial.printf("[%lu] [WEB] Delete failed - item not found: %s\n", millis(), itemPath.c_str()); + server->send(404, "text/plain", "Item not found"); + return; + } + + Serial.printf("[%lu] [WEB] Attempting to delete %s: %s\n", millis(), itemType.c_str(), itemPath.c_str()); + + bool success = false; + + if (itemType == "folder") { + // For folders, try to remove (will fail if not empty) + File dir = SD.open(itemPath.c_str()); + if (dir && dir.isDirectory()) { + // Check if folder is empty + File entry = dir.openNextFile(); + if (entry) { + // Folder is not empty + entry.close(); + dir.close(); + Serial.printf("[%lu] [WEB] Delete failed - folder not empty: %s\n", millis(), itemPath.c_str()); + server->send(400, "text/plain", "Folder is not empty. Delete contents first."); + return; + } + dir.close(); + } + success = SD.rmdir(itemPath.c_str()); + } else { + // For files, use remove + success = SD.remove(itemPath.c_str()); + } + + if (success) { + Serial.printf("[%lu] [WEB] Successfully deleted: %s\n", millis(), itemPath.c_str()); + server->send(200, "text/plain", "Deleted successfully"); + } else { + Serial.printf("[%lu] [WEB] Failed to delete: %s\n", millis(), itemPath.c_str()); + server->send(500, "text/plain", "Failed to delete item"); + } +} diff --git a/src/CrossPointWebServer.h b/src/CrossPointWebServer.h index cce551a..366f931 100644 --- a/src/CrossPointWebServer.h +++ b/src/CrossPointWebServer.h @@ -51,6 +51,7 @@ class CrossPointWebServer { void handleUpload(); void handleUploadPost(); void handleCreateFolder(); + void handleDelete(); }; // Global instance
NameTypeSize
NameTypeSizeActions
" + icon + ""; - html += "" + file.name + "" + badge + "" + typeStr + "" + sizeStr + "
" + icon + "" + file.name + badge + "" + icon + "" + escapeHtml(file.name) + badge + "" + typeStr + "" + sizeStr + "