diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index ffb20a6..45ae237 100644 Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ diff --git a/routes/__pycache__/room_files.cpython-313.pyc b/routes/__pycache__/room_files.cpython-313.pyc index 2ac788d..3c18ed3 100644 Binary files a/routes/__pycache__/room_files.cpython-313.pyc and b/routes/__pycache__/room_files.cpython-313.pyc differ diff --git a/routes/__pycache__/trash.cpython-313.pyc b/routes/__pycache__/trash.cpython-313.pyc index 87d9d5c..3b39dbf 100644 Binary files a/routes/__pycache__/trash.cpython-313.pyc and b/routes/__pycache__/trash.cpython-313.pyc differ diff --git a/routes/room_files.py b/routes/room_files.py index e7d11d8..294710c 100644 --- a/routes/room_files.py +++ b/routes/room_files.py @@ -652,14 +652,16 @@ def delete_permanent(room_id): if not rf: continue - # Delete the file from storage if it's a file - if rf.type == 'file': - try: - file_path = os.path.join(get_room_dir(room_id), rf.path, rf.name) - if os.path.exists(file_path): + # Delete the file/folder from storage + try: + file_path = os.path.join(get_room_dir(room_id), rf.path, rf.name) + if os.path.exists(file_path): + if rf.type == 'file': os.remove(file_path) - except Exception as e: - print(f"Error deleting file from storage: {e}") + elif rf.type == 'folder': + shutil.rmtree(file_path) + except Exception as e: + print(f"Error deleting {rf.type} from storage: {e}") # Delete the database record db.session.delete(rf) diff --git a/routes/trash.py b/routes/trash.py index 215eec0..ee8726e 100644 --- a/routes/trash.py +++ b/routes/trash.py @@ -103,8 +103,25 @@ def empty_trash(room_id): if not user_has_permission(room, 'can_delete'): abort(403) - # Delete all trashed files for this room + # Get all trashed files for this room + trashed_files = TrashedFile.query.filter_by(room_id=room_id).all() + room_files = RoomFile.query.filter_by(room_id=room_id, deleted=True).all() + + # Delete physical files + room_dir = os.path.join('/data/rooms', str(room_id)) + for file in trashed_files + room_files: + try: + if file.type == 'file': + file_path = os.path.join(room_dir, file.original_path if hasattr(file, 'original_path') else file.path, file.name) + if os.path.exists(file_path): + os.remove(file_path) + except Exception as e: + print(f"Error deleting physical file {file.name}: {str(e)}") + continue + + # Delete all trashed files from both tables TrashedFile.query.filter_by(room_id=room_id).delete() + RoomFile.query.filter_by(room_id=room_id, deleted=True).delete() db.session.commit() return jsonify({'success': True}) \ No newline at end of file diff --git a/static/js/trash.js b/static/js/trash.js index c5bb932..28e7287 100644 --- a/static/js/trash.js +++ b/static/js/trash.js @@ -32,6 +32,13 @@ window.emptyTrash = function() { fetch('/api/rooms/trash') .then(r => r.json()) .then(files => { + if (!files || !files.length) { + // No files to delete, just close the modal + const modal = bootstrap.Modal.getInstance(document.getElementById('emptyTrashModal')); + modal.hide(); + return Promise.resolve([]); + } + // Get unique room IDs const roomIds = [...new Set(files.map(file => file.room_id))]; @@ -61,12 +68,20 @@ window.emptyTrash = function() { // Close the modal const modal = bootstrap.Modal.getInstance(document.getElementById('emptyTrashModal')); modal.hide(); + // Refresh the view to ensure everything is up to date + fetchFiles(); } else { console.error('Failed to empty trash in some rooms'); + // Show error message to user + const grid = document.getElementById('fileGrid'); + grid.innerHTML = '
Failed to empty trash. Please try again.
'; } }) .catch(error => { console.error('Error emptying trash:', error); + // Show error message to user + const grid = document.getElementById('fileGrid'); + grid.innerHTML = '
Error emptying trash. Please try again.
'; }); }; diff --git a/templates/rooms/room.html b/templates/rooms/room.html index 9de7de1..6eee98f 100644 --- a/templates/rooms/room.html +++ b/templates/rooms/room.html @@ -1360,7 +1360,9 @@ document.addEventListener('DOMContentLoaded', function() { moveModal = new bootstrap.Modal(document.getElementById('moveModal')); // Add click handler for move confirmation button - document.getElementById('confirmMoveBtn').addEventListener('click', moveFileConfirmed); + if (canMove === true || canMove === 'true') { + document.getElementById('confirmMoveBtn').addEventListener('click', moveFileConfirmed); + } // Add click handler for new folder button if (canUpload === true || canUpload === 'true') { @@ -1715,126 +1717,140 @@ document.addEventListener('DOMContentLoaded', function() { // Start processing files await processNextFile(); } - - // Robust modal show logic - function showOverwriteModal(filename) { - console.log('[Modal] showOverwriteModal called for', filename); - - // If there's an existing modal promise, resolve it with skip - if (modalResolve) { - modalResolve('skip'); - modalResolve = null; - } - - // Create a new promise for this modal interaction - modalPromise = new Promise((resolve) => { - modalResolve = resolve; - - // Update modal content - document.getElementById('overwriteFileName').textContent = filename; - - // Get modal elements - const modalEl = document.getElementById('overwriteConfirmModal'); - - // Ensure modal is properly initialized with static backdrop - if (!overwriteModal) { - overwriteModal = new bootstrap.Modal(modalEl, { - backdrop: 'static', - keyboard: false - }); - } - - // Get fresh button references - const confirmOverwriteBtn = document.getElementById('confirmOverwriteBtn'); - const skipOverwriteBtn = document.getElementById('skipOverwriteBtn'); - const confirmAllOverwriteBtn = document.getElementById('confirmAllOverwriteBtn'); - const skipAllOverwriteBtn = document.getElementById('skipAllOverwriteBtn'); - - // Remove any existing event listeners by cloning and replacing - const newConfirmOverwriteBtn = confirmOverwriteBtn.cloneNode(true); - const newSkipOverwriteBtn = skipOverwriteBtn.cloneNode(true); - const newConfirmAllOverwriteBtn = confirmAllOverwriteBtn.cloneNode(true); - const newSkipAllOverwriteBtn = skipAllOverwriteBtn.cloneNode(true); - - confirmOverwriteBtn.parentNode.replaceChild(newConfirmOverwriteBtn, confirmOverwriteBtn); - skipOverwriteBtn.parentNode.replaceChild(newSkipOverwriteBtn, skipOverwriteBtn); - confirmAllOverwriteBtn.parentNode.replaceChild(newConfirmAllOverwriteBtn, confirmAllOverwriteBtn); - skipAllOverwriteBtn.parentNode.replaceChild(newSkipAllOverwriteBtn, skipAllOverwriteBtn); - - let modalClosed = false; - - // Function to handle modal resolution - const resolveModal = (choice) => { - if (!modalClosed) { - modalClosed = true; - if (modalResolve) { - modalResolve(choice); - modalResolve = null; - } - overwriteModal.hide(); - } - }; - - // Add click handlers directly to the buttons - newConfirmOverwriteBtn.onclick = () => { - console.log('[Modal] Overwrite clicked'); - resolveModal('overwrite'); - }; - - newSkipOverwriteBtn.onclick = () => { - console.log('[Modal] Skip clicked'); - resolveModal('skip'); - }; - - newConfirmAllOverwriteBtn.onclick = () => { - console.log('[Modal] Overwrite All clicked'); - overwriteAll = true; - resolveModal('overwrite_all'); - }; - - newSkipAllOverwriteBtn.onclick = () => { - console.log('[Modal] Skip All clicked'); - skipAll = true; - resolveModal('skip_all'); - }; - - // Prevent modal from being closed by clicking outside or pressing escape - modalEl.addEventListener('click', (e) => { - if (e.target === modalEl) { - e.preventDefault(); - e.stopPropagation(); - } - }); - - // Add modal close handler - const handleModalClose = (e) => { - if (!modalClosed && modalResolve) { - console.log('[Modal] Modal closed without explicit choice'); - e.preventDefault(); - e.stopPropagation(); - // Re-show the modal if it was closed without a choice - overwriteModal.show(); - } - }; - - // Remove any existing close handler and add new one - modalEl.removeEventListener('hidden.bs.modal', handleModalClose); - modalEl.addEventListener('hidden.bs.modal', handleModalClose); - - // Show the modal - overwriteModal.show(); - - // Ensure modal is visible and focused - modalEl.style.display = 'block'; - modalEl.classList.add('show'); - modalEl.focus(); - }); - - return modalPromise; - } } }); +// Add event listener for delete confirmation button +if (canDelete === true || canDelete === 'true') { + document.getElementById('confirmDeleteBtn').addEventListener('click', deleteFileConfirmed); +} + +// Add event listener for rename confirmation button +if (canRename === true || canRename === 'true') { + document.getElementById('confirmRenameBtn').addEventListener('click', function() { + if (!renameTarget) return; + let newName = document.getElementById('renameInput').value.trim(); + if (renameIsFile) { + if (!newName) { + document.getElementById('renameError').textContent = 'New name is required.'; + return; + } + newName = newName + renameOrigExt; + } + const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + if (!newName) { + document.getElementById('renameError').textContent = 'New name is required.'; + return; + } + fetch(`/api/rooms/${roomId}/rename`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + body: JSON.stringify({ + old_name: renameTarget, + new_name: newName, + path: currentPath + }) + }) + .then(r => r.json()) + .then(res => { + if (res.success) { + fetchFiles(); + var modalEl = document.getElementById('renameModal'); + var modal = bootstrap.Modal.getInstance(modalEl); + modal.hide(); + } else { + document.getElementById('renameError').textContent = res.error || 'Rename failed.'; + } + }) + .catch(() => { + document.getElementById('renameError').textContent = 'Rename failed.'; + }); + }); +} + +// Add event listener for download selected +if (canDownload === true || canDownload === 'true') { + document.getElementById('downloadSelectedBtn').addEventListener('click', function() { + const selectedCheckboxes = document.querySelectorAll('.select-item-checkbox:checked'); + if (selectedCheckboxes.length === 0) return; + + const selectedItems = Array.from(selectedCheckboxes).map(cb => { + const idx = parseInt(cb.dataset.idx); + return window.currentFiles[idx]; + }); + + // Submit the request to download the zip + const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + fetch(`/api/rooms/${roomId}/download-zip`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + body: JSON.stringify({ items: selectedItems }) + }) + .then(response => { + if (!response.ok) { + throw new Error('Download failed'); + } + return response.blob(); + }) + .then(blob => { + // Create a download link and trigger it + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'download.zip'; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + a.remove(); + }) + .catch(error => { + console.error('Error downloading files:', error); + document.getElementById('fileError').textContent = 'Failed to download files.'; + }); + }); +} + +// Add event listener for batch delete +if (canDelete === true || canDelete === 'true') { + document.getElementById('deleteSelectedBtn').addEventListener('click', function() { + const selectedCheckboxes = document.querySelectorAll('.select-item-checkbox:checked'); + if (selectedCheckboxes.length === 0) return; + + batchDeleteItems = Array.from(selectedCheckboxes).map(cb => { + const idx = parseInt(cb.dataset.idx); + return window.currentFiles[idx]; + }); + + // Get the modal element + const modalEl = document.getElementById('deleteConfirmModal'); + if (!modalEl) { + console.error('Delete modal element not found'); + return; + } + + // Initialize the modal if it hasn't been initialized + if (!deleteModal) { + deleteModal = new bootstrap.Modal(modalEl); + } + + // Update modal content + const fileNameEl = document.getElementById('deleteFileName'); + const labelEl = document.getElementById('deleteConfirmLabel'); + + if (fileNameEl) fileNameEl.textContent = `${selectedCheckboxes.length} item${selectedCheckboxes.length > 1 ? 's' : ''}`; + if (labelEl) labelEl.textContent = 'Move to Trash'; + + // Show the modal + deleteModal.show(); + }); +} + function navigateToParent() { if (!currentPath) return; const parts = currentPath.split('/'); diff --git a/uploads/13/20250526_145757_Kobe_Amerijckx.docx b/uploads/13/20250526_145757_Kobe_Amerijckx.docx deleted file mode 100644 index ed98e52..0000000 Binary files a/uploads/13/20250526_145757_Kobe_Amerijckx.docx and /dev/null differ diff --git a/uploads/15/20250526_150145_logo-light.png b/uploads/15/20250526_150145_logo-light.png deleted file mode 100644 index ef91431..0000000 Binary files a/uploads/15/20250526_150145_logo-light.png and /dev/null differ diff --git a/uploads/15/20250526_150153_logo-light.png b/uploads/15/20250526_150153_logo-light.png deleted file mode 100644 index ef91431..0000000 Binary files a/uploads/15/20250526_150153_logo-light.png and /dev/null differ diff --git a/uploads/15/20250526_150153_logo-placeholder.png b/uploads/15/20250526_150153_logo-placeholder.png deleted file mode 100644 index 2bcaf7d..0000000 Binary files a/uploads/15/20250526_150153_logo-placeholder.png and /dev/null differ