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