Files
CDN-APP-INSIDER/public/js/folder.js
Dinawo 2df1b28962
All checks were successful
continuous-integration/drone/push Build is passing
Update v1.2.0-beta - Dynamic context menu & permissions
 New Features:
- Dynamic permission-based context menus for files and folders
- Support for collaborative folder access control
- Upload to specific folders including shared folders
- Changelog modal for version updates
- Improved dark mode synchronization

🐛 Bug Fixes:
- Fixed context menu displaying incorrect options
- Fixed CSS !important override preventing dynamic menu behavior
- Fixed folder collaboration permission checks
- Fixed breadcrumb navigation with empty segments
- Fixed "Premature close" error loop in attachments
- Fixed missing user variable in admin routes
- Fixed avatar loading COEP policy issues

🔒 Security:
- Added security middleware (CSRF, rate limiting, input validation)
- Fixed collaboration folder access validation
- Improved shared folder permission handling

🎨 UI/UX Improvements:
- Removed Actions column from folder view
- Context menu now properly hides/shows based on permissions
- Better visual feedback for collaborative folders
- Improved upload flow with inline modals

🧹 Code Quality:
- Added collaboration data to folder routes
- Refactored context menu logic for better maintainability
- Added debug logging for troubleshooting
- Improved file upload handling with chunking support
2025-10-25 23:55:51 +02:00

418 lines
16 KiB
JavaScript

function calculateFolderSize(contents) {
let totalSize = 0;
contents.forEach(file => {
if (file.type === 'file' && file.size !== undefined && !isNaN(file.size) && file.size >= 0) {
totalSize += file.size;
} else if (file.type === 'folder' && file.contents !== undefined) {
totalSize += calculateFolderSize(file.contents);
}
});
return totalSize;
}
document.addEventListener('DOMContentLoaded', function () {
// La fonctionnalité de copie de lien est maintenant gérée dans folder.ejs
const filterForm = document.getElementById('filterForm');
const extensionFilter = document.getElementById('extensionFilter');
const fileSearchInput = document.getElementById('fileSearch');
const styleSwitcherButton = document.getElementById('styleSwitcher');
const icon = document.getElementById('themeIcon');
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
let isDarkMode = darkModeMediaQuery.matches;
function toggleDarkMode() {
isDarkMode = !isDarkMode;
document.body.classList.toggle('dark-mode', isDarkMode);
if (!icon) return; // Vérifier que l'élément existe avant de l'utiliser
if (isDarkMode) {
icon.classList.remove('bi-brightness-high-fill');
icon.classList.add('bi-moon-fill');
icon.innerHTML = '</svg><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-brightness-high-fill" viewBox="0 0 16 16"><path d="M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0M8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0m0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13m8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5M3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8m10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0m-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0m9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707M4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708"/></svg>';
} else {
icon.classList.remove('bi-moon-fill');
icon.classList.add('bi-brightness-high-fill');
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-moon-fill" viewBox="0 0 16 16"><path d="M6 .278a.77.77 0 0 1 .08.858 7.2 7.2 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277q.792-.001 1.533-.16a.79.79 0 0 1 .81.316.73.73 0 0 1-.031.893A8.35 8.35 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.75.75 0 0 1 6 .278"/>';
}
}
function applyStyleMode() {
document.body.classList.toggle('dark-mode', isDarkMode);
if (!icon) return; // Vérifier que l'élément existe avant de l'utiliser
if (isDarkMode) {
icon.classList.remove('bi-brightness-high-fill');
icon.classList.add('bi-moon-fill');
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-brightness-high-fill" viewBox="0 0 16 16"><path d="M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0M8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0m0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13m8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5M3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8m10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0m-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0m9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707M4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708"/></svg>';
} else {
icon.classList.remove('bi-moon-fill');
icon.classList.add('bi-brightness-high-fill');
icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-moon-fill" viewBox="0 0 16 16"><path d="M6 .278a.77.77 0 0 1 .08.858 7.2 7.2 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277q.792-.001 1.533-.16a.79.79 0 0 1 .81.316.73.73 0 0 1-.031.893A8.35 8.35 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.75.75 0 0 1 6 .278"/></svg>';
}
}
darkModeMediaQuery.addListener(applyStyleMode);
applyStyleMode();
if (styleSwitcherButton) {
styleSwitcherButton.addEventListener('click', toggleDarkMode);
}
if (filterForm) {
filterForm.addEventListener('submit', function (event) {
event.preventDefault();
const selectedExtension = extensionFilter ? extensionFilter.value.toLowerCase() : '';
const searchQuery = fileSearchInput ? fileSearchInput.value.toLowerCase() : '';
const fileList = document.querySelectorAll('tr[data-extension]');
fileList.forEach(file => {
const fileExtension = file.getAttribute('data-extension').toLowerCase();
const fileName = file.querySelector('td:first-child').textContent.toLowerCase();
const extensionMatch = selectedExtension === '' || selectedExtension === fileExtension;
const searchMatch = fileName.includes(searchQuery);
if (extensionMatch && searchMatch) {
file.style.display = '';
} else {
file.style.display = 'none';
}
});
});
}
});
async function confirmDeleteFile(folderName, filename) {
const requestBody = JSON.stringify({
filename: filename,
});
const confirmationResult = await Swal.fire({
title: 'Êtes-vous sûr de vouloir supprimer ce fichier?',
text: 'Cette action est irréversible!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Supprimer'
});
if (confirmationResult.isConfirmed) {
try {
const response = await fetch(`/api/dpanel/dashboard/deletefile/${folderName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: requestBody,
});
const responseData = await response.json();
if (response.ok) {
handleDeleteSuccess();
} else {
handleDeleteFailure(response, responseData);
}
} catch (error) {
console.error('Erreur lors de la suppression du fichier:', error);
handleDeleteFailure();
}
}
}
function handleDeleteSuccess() {
Swal.fire({
position: 'top',
icon: 'success',
title: 'Le fichier a été supprimé avec succès.',
showConfirmButton: false,
timer: 1800,
toast: true
})
.then(() => {
location.reload();
});
}
function handleDeleteFailure(response, responseData) {
if (response.status === 404 && responseData && responseData.status === 'error') {
Swal.fire({
position: 'top',
icon: 'error',
title: responseData.message,
showConfirmButton: false,
timer: 1800,
toast: true
})
} else {
Swal.fire({
position: 'top',
icon: 'error',
title: 'La suppression du fichier a échoué.',
showConfirmButton: false,
timer: 1800,
toast: true
})
}
}
function getCurrentFolderName() {
const currentPath = window.location.pathname;
const pathSegments = currentPath.split('/');
const currentFolderName = pathSegments[pathSegments.length - 1];
return currentFolderName;
}
function renameFolder(currentName, newName) {
try {
const currentFolderName = getCurrentFolderName();
const renameURL = `/api/dpanel/dashboard/renamefile/${currentFolderName}`;
fetch(renameURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ newName: newName, currentName: currentName }),
})
.then(response => response.json())
.then(data => {
if (data.renamed) {
Swal.fire({
position: 'top',
icon: 'errsuccessor',
title: 'Succès',
showConfirmButton: false,
timer: 1800,
toast: true,
}).then(() => {
location.reload();
});
} else {
throw new Error(data.error);
}
})
.catch(error => {
console.error('Erreur lors du renommage du fichier:', error);
Swal.fire({
position: 'top',
icon: 'error',
title: error.message || 'Une erreur est survenue lors du renommage du fichier.',
showConfirmButton: false,
timer: 1800,
toast: true
})
});
} catch (error) {
console.error('Erreur lors du renommage du fichier:', error);
}
}
function renameFile(folderName, currentName) {
const fileExtensionIndex = currentName.lastIndexOf('.');
const fileExtension = currentName.substring(fileExtensionIndex);
Swal.fire({
title: 'Entrez le nouveau nom',
input: 'text',
inputValue: currentName,
inputPlaceholder: 'Nouveau nom',
showCancelButton: true,
confirmButtonText: 'Renommer',
cancelButtonText: 'Annuler',
onOpen: (el) => {
setTimeout(() => {
const input = Swal.getInput();
const pos = input.value.lastIndexOf('.');
input.setSelectionRange(0, pos);
}, 0);
}
}).then((result) => {
if (result.isConfirmed) {
const newName = result.value;
fetch(`/api/dpanel/dashboard/rename/${folderName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ currentName: currentName, newName: newName }),
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text();
})
.then(data => {
Swal.fire({
position: 'top',
icon: 'success',
title: 'Le fichier a été renommé avec succès.',
showConfirmButton: false,
timer: 1800,
toast: true,
}).then(() => {
location.reload();
});
})
.catch((error) => {
Swal.fire({
position: 'top',
icon: 'error',
title: 'Erreur lors du renommage du fichier.',
showConfirmButton: false,
timer: 1800,
toast: true,
});
});
}
});
}
async function showFileInfo(fileName) {
let data;
try {
let response = await fetch('/file_info.json');
if (!response.ok) {
throw new Error('Network response was not ok');
}
data = await response.json();
} catch (error) {
Swal.fire({
position: 'top',
icon: 'error',
title: 'Les informations sur les fichiers ne sont pas disponibles pour le moment. Veuillez réessayer plus tard.',
showConfirmButton: false,
timer: 1800,
toast: true,
});
return;
}
let fileInfo = data.find(file => file.fileName === fileName);
if (!fileInfo) {
Swal.fire({
position: 'top',
icon: 'error',
title: `Aucune information trouvée pour le fichier ${fileName}.`,
showConfirmButton: false,
timer: 1800,
toast: true,
});
return;
}
let html = `<p>Nom du fichier : ${fileInfo.fileName}</p>`;
if (fileInfo.expiryDate) {
html += `<p>Date de fin de disponibilité : ${fileInfo.expiryDate}</p>`;
}
if (fileInfo.password) {
html += `<p>Mot de passe : Oui</p>`;
}
Swal.fire({
title: 'Informations sur le fichier',
html: html,
confirmButtonText: 'Fermer'
});
}document.addEventListener('DOMContentLoaded', function () {
const moveFileForm = document.getElementById('moveFileForm');
moveFileForm.addEventListener('submit', function (event) {
event.preventDefault();
const fileName = this.querySelector('input[name="fileName"]').value;
const userName = this.querySelector('input[name="userName"]').value;
const oldFolderName = this.querySelector('input[name="oldFolderName"]').value;
const newFolderName = this.querySelector('select[name="newFolderName"]').value;
if (!newFolderName || newFolderName === "Déplacer vers...") {
Swal.fire({
position: 'top',
icon: 'error',
title: 'Veuillez sélectionner un dossier de destination',
showConfirmButton: false,
timer: 1800,
toast: true
});
return;
}
Swal.fire({
title: 'Confirmer le déplacement du fichier',
text: `Voulez-vous déplacer le fichier ${fileName} vers ${newFolderName} ?`,
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Déplacer',
cancelButtonText: 'Annuler',
}).then((result) => {
if (result.isConfirmed) {
fetch(`/api/dpanel/dashboard/movefile/${oldFolderName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ fileName, userName, newFolderName }),
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.message === "File moved successfully") {
Swal.fire({
position: 'top',
icon: 'success',
title: 'Le fichier a été déplacé avec succès.',
showConfirmButton: false,
timer: 1800,
toast: true,
}).then(() => {
location.reload();
});
} else {
throw new Error(data.error || 'Une erreur est survenue');
}
})
.catch((error) => {
Swal.fire({
position: 'top',
icon: 'error',
title: 'Erreur lors du déplacement du fichier.',
showConfirmButton: false,
timer: 1800,
toast: true,
});
});
}
});
});
});
// Fetch version from build-metadata API
fetch('/build-metadata')
.then(response => response.json())
.then(data => {
document.getElementById('version-number').textContent = data.build_version;
})
.catch(error => {
console.error('Error fetching version:', error);
document.getElementById('version-number').textContent = 'Version indisponible';
});