From c880891839347891345d6afb1a03cb9cd2a2a961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=91=D0=BB=D0=B8?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Fri, 29 May 2026 12:52:51 +0300 Subject: [PATCH] Add folder deletion with confirmation dialog --- backend/main.py | 15 +++++++++++++ backend/static/app.js | 45 +++++++++++++++++++++++++++++++++++++-- backend/static/styles.css | 15 +++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/backend/main.py b/backend/main.py index d6721ba..5a66f54 100644 --- a/backend/main.py +++ b/backend/main.py @@ -197,5 +197,20 @@ async def api_download(path: str): return FileResponse(file_path, filename=file_path.name) +import shutil + +@app.delete("/api/folders/{folder_name}") +async def api_delete_folder(folder_name: str): + """Удаляет папку с обработанными файлами.""" + folder_path = PROCESSED_DIR / folder_name + if not folder_path.exists(): + return {"error": "Folder not found"} + try: + shutil.rmtree(folder_path) + return {"deleted": folder_name} + except Exception as e: + return {"error": str(e)} + + # Статические файлы app.mount("/static", StaticFiles(directory="backend/static"), name="static") diff --git a/backend/static/app.js b/backend/static/app.js index cbcdac7..036c44b 100644 --- a/backend/static/app.js +++ b/backend/static/app.js @@ -240,12 +240,13 @@ class TranscriptionApp { }).join(''); return ` -
+
📁 ${this.escapeHtml(folder.name)} ${this.formatDate(folder.created)} + 🗑️
${files} @@ -258,8 +259,9 @@ class TranscriptionApp { const container = document.getElementById('fileTree'); container.addEventListener('click', (e) => { + // Toggle folder const folderHeader = e.target.closest('.folder-header'); - if (folderHeader) { + if (folderHeader && !e.target.closest('.folder-delete')) { const folder = folderHeader.closest('.folder-item'); const files = folder.querySelector('.folder-files'); const toggle = folderHeader.querySelector('.folder-toggle'); @@ -273,6 +275,16 @@ class TranscriptionApp { } } + // Delete folder + const deleteBtn = e.target.closest('.folder-delete'); + if (deleteBtn) { + const folderItem = deleteBtn.closest('.folder-item'); + const folderName = folderItem.dataset.folder; + this.confirmDeleteFolder(folderName, folderItem); + return; + } + + // Open file const fileItem = e.target.closest('.file-item.clickable'); if (fileItem) { const path = fileItem.dataset.path; @@ -282,6 +294,35 @@ class TranscriptionApp { }); } + confirmDeleteFolder(folderName, folderElement) { + if (confirm(`Удалить папку "${folderName}" со всеми файлами?\n\nЭто действие нельзя отменить.`)) { + this.deleteFolder(folderName, folderElement); + } + } + + async deleteFolder(folderName, folderElement) { + try { + const response = await fetch(`/api/folders/${encodeURIComponent(folderName)}`, { + method: 'DELETE', + }); + const result = await response.json(); + + if (result.error) { + this.showToast(`Ошибка удаления: ${result.error}`, 'error'); + } else { + this.showToast(`Папка "${folderName}" удалена`, 'success'); + folderElement.remove(); + // Refresh tree if empty + const container = document.getElementById('fileTree'); + if (!container.querySelector('.folder-item')) { + container.innerHTML = '

Нет обработанных файлов

'; + } + } + } catch (error) { + this.showToast(`Ошибка удаления: ${error.message}`, 'error'); + } + } + // ===== Viewer ===== async loadFileContent(path, ext) { const viewer = document.getElementById('viewer'); diff --git a/backend/static/styles.css b/backend/static/styles.css index afd7f1b..72005c9 100644 --- a/backend/static/styles.css +++ b/backend/static/styles.css @@ -254,6 +254,21 @@ header h1 { color: var(--text-secondary); } +.folder-delete { + font-size: 1rem; + cursor: pointer; + padding: 2px 6px; + border-radius: 4px; + transition: background 0.2s; + margin-left: 8px; + opacity: 0.6; +} + +.folder-delete:hover { + background: rgba(248, 113, 113, 0.2); + opacity: 1; +} + .folder-files { margin-left: 24px; padding-left: 8px;