Add folder deletion with confirmation dialog

This commit is contained in:
Кирилл Блинов 2026-05-29 12:52:51 +03:00
parent 8bb21d0d7f
commit c880891839
3 changed files with 73 additions and 2 deletions

View File

@ -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")

View File

@ -240,12 +240,13 @@ class TranscriptionApp {
}).join('');
return `
<div class="folder-item">
<div class="folder-item" data-folder="${this.escapeHtml(folder.name)}">
<div class="folder-header">
<span class="folder-toggle"></span>
<span class="folder-icon">📁</span>
<span class="folder-name">${this.escapeHtml(folder.name)}</span>
<span class="folder-date">${this.formatDate(folder.created)}</span>
<span class="folder-delete" title="Удалить папку">🗑</span>
</div>
<div class="folder-files">
${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 = '<p class="empty-state">Нет обработанных файлов</p>';
}
}
} catch (error) {
this.showToast(`Ошибка удаления: ${error.message}`, 'error');
}
}
// ===== Viewer =====
async loadFileContent(path, ext) {
const viewer = document.getElementById('viewer');

View File

@ -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;