Copy/Move functionality
This commit is contained in:
25
README.md
25
README.md
@@ -3,13 +3,19 @@
|
||||
|
||||
<img src="https://raw.githubusercontent.com/error311/multi-file-upload-editor/refs/heads/master/resources/main-screen.png" alt="main screen">
|
||||
|
||||
**Changes 3/3/2025:**
|
||||
**Changes 3/4/2025:**
|
||||
Copy & Move functionality added
|
||||
Header Layout
|
||||
Modal Popups (Edit, Add User, Remove User) changes
|
||||
Consolidated table styling
|
||||
CSS Consolidation
|
||||
assets folder
|
||||
|
||||
folder management added
|
||||
|
||||
some refactoring
|
||||
|
||||
config added USERS_DIR & USERS_FILE
|
||||
|
||||
**Changes 3/3/2025:**
|
||||
folder management added
|
||||
some refactoring
|
||||
config added USERS_DIR & USERS_FILE
|
||||
|
||||
# Multi File Upload & Edit
|
||||
|
||||
@@ -110,15 +116,10 @@ This project is a lightweight, secure web application for uploading, editing, an
|
||||
|
||||
|
||||
|
||||
fork of:
|
||||
based off of:
|
||||
https://github.com/sensboston/uploader
|
||||
|
||||
|
||||
# File Uploader
|
||||
|
||||
A simple file uploader web app that allows authenticated users to upload, list, and delete files.
|
||||
The application uses PHP, running on Apache2, Ubuntu (but definitely should work everywhere).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Apache2, configured, up and running
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -1,8 +1,10 @@
|
||||
// displayFileList.js
|
||||
|
||||
import { sendRequest, toggleVisibility } from './utils.js';
|
||||
|
||||
let fileData = [];
|
||||
let sortOrder = { column: "uploaded", ascending: false };
|
||||
export let currentFolder = "root"; // Global current folder
|
||||
|
||||
export function loadFileList() {
|
||||
sendRequest("checkAuth.php")
|
||||
@@ -13,7 +15,7 @@ export function loadFileList() {
|
||||
return;
|
||||
}
|
||||
toggleVisibility("fileListContainer", true);
|
||||
return sendRequest("getFileList.php");
|
||||
return sendRequest("getFileList.php?folder=" + encodeURIComponent(currentFolder));
|
||||
})
|
||||
.then(data => {
|
||||
if (!data) return;
|
||||
@@ -26,111 +28,23 @@ export function loadFileList() {
|
||||
return;
|
||||
}
|
||||
fileData = data.files;
|
||||
sortFiles("uploaded", false);
|
||||
//sortFiles("uploaded", false);
|
||||
})
|
||||
.catch(error => console.error("Error loading file list:", error));
|
||||
}
|
||||
|
||||
export function sortFiles(column, forceAscending = null) {
|
||||
if (sortOrder.column === column) {
|
||||
sortOrder.ascending = forceAscending !== null ? forceAscending : !sortOrder.ascending;
|
||||
} else {
|
||||
sortOrder.column = column;
|
||||
sortOrder.ascending = forceAscending !== null ? forceAscending : true;
|
||||
}
|
||||
fileData.sort((a, b) => {
|
||||
let valA = a[column] || "";
|
||||
let valB = b[column] || "";
|
||||
if (column === "modified" || column === "uploaded") {
|
||||
const dateA = new Date(valA);
|
||||
const dateB = new Date(valB);
|
||||
if (!isNaN(dateA.getTime()) && !isNaN(dateB.getTime())) {
|
||||
valA = dateA.getTime();
|
||||
valB = dateB.getTime();
|
||||
} else {
|
||||
valA = valA.toLowerCase();
|
||||
valB = valB.toLowerCase();
|
||||
}
|
||||
} else if (typeof valA === "string") {
|
||||
valA = valA.toLowerCase();
|
||||
valB = valB.toLowerCase();
|
||||
}
|
||||
if (valA < valB) return sortOrder.ascending ? -1 : 1;
|
||||
if (valA > valB) return sortOrder.ascending ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
renderFileTable();
|
||||
}
|
||||
|
||||
export function renderFileTable() {
|
||||
const fileListContainer = document.getElementById("fileList");
|
||||
let tableHTML = `<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" id="selectAll" onclick="toggleAllCheckboxes(this)"></th>
|
||||
<th onclick="sortFiles('name')" style="cursor:pointer; text-decoration: underline; white-space: nowrap;">
|
||||
<span>File Name</span> <span>${sortOrder.column === "name" ? (sortOrder.ascending ? "▲" : "▼") : ""}</span>
|
||||
</th>
|
||||
<th onclick="sortFiles('modified')" style="cursor:pointer; text-decoration: underline; white-space: nowrap;">
|
||||
<span>Date Modified</span> <span>${sortOrder.column === "modified" ? (sortOrder.ascending ? "▲" : "▼") : ""}</span>
|
||||
</th>
|
||||
<th onclick="sortFiles('uploaded')" style="cursor:pointer; text-decoration: underline; white-space: nowrap;">
|
||||
<span>Upload Date</span> <span>${sortOrder.column === "uploaded" ? (sortOrder.ascending ? "▲" : "▼") : ""}</span>
|
||||
</th>
|
||||
<th onclick="sortFiles('size')" style="cursor:pointer; text-decoration: underline; white-space: nowrap;">
|
||||
<span>File Size</span> <span>${sortOrder.column === "size" ? (sortOrder.ascending ? "▲" : "▼") : ""}</span>
|
||||
</th>
|
||||
<th onclick="sortFiles('uploader')" style="cursor:pointer; text-decoration: underline; white-space: nowrap;">
|
||||
<span>Uploader</span> <span>${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""}</span>
|
||||
</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>`;
|
||||
|
||||
fileData.forEach(file => {
|
||||
const isEditable = file.name.endsWith(".txt") || file.name.endsWith(".json") ||
|
||||
file.name.endsWith(".ini") || file.name.endsWith(".css") ||
|
||||
file.name.endsWith(".js") || file.name.endsWith(".csv") ||
|
||||
file.name.endsWith(".md") || file.name.endsWith(".xml") ||
|
||||
file.name.endsWith(".html") || file.name.endsWith(".py") ||
|
||||
file.name.endsWith(".log") || file.name.endsWith(".conf") ||
|
||||
file.name.endsWith(".config") || file.name.endsWith(".bat") ||
|
||||
file.name.endsWith(".rtf") || file.name.endsWith(".doc") ||
|
||||
file.name.endsWith(".docx");
|
||||
tableHTML += `<tr>
|
||||
<td><input type="checkbox" class="file-checkbox" value="${file.name}" onclick="toggleDeleteButton()"></td>
|
||||
<td>${file.name}</td>
|
||||
<td style="white-space: nowrap;">${file.modified}</td>
|
||||
<td style="white-space: nowrap;">${file.uploaded}</td>
|
||||
<td style="white-space: nowrap;">${file.size}</td>
|
||||
<td style="white-space: nowrap;">${file.uploader || "Unknown"}</td>
|
||||
<td>
|
||||
<div style="display: inline-flex; align-items: center; gap: 5px; flex-wrap: nowrap;">
|
||||
<a href="uploads/${file.name}" download>Download</a>
|
||||
${isEditable ? `<button onclick="editFile('${file.name}')">Edit</button>` : ""}
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
tableHTML += `</tbody></table>`;
|
||||
fileListContainer.innerHTML = tableHTML;
|
||||
|
||||
const deleteBtn = document.getElementById("deleteSelectedBtn");
|
||||
if (fileData.length > 0) {
|
||||
deleteBtn.style.display = "block";
|
||||
const selectedFiles = document.querySelectorAll(".file-checkbox:checked");
|
||||
deleteBtn.disabled = selectedFiles.length === 0;
|
||||
} else {
|
||||
deleteBtn.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function toggleDeleteButton() {
|
||||
const selectedFiles = document.querySelectorAll(".file-checkbox:checked");
|
||||
const deleteBtn = document.getElementById("deleteSelectedBtn");
|
||||
deleteBtn.disabled = selectedFiles.length === 0;
|
||||
const copyBtn = document.getElementById("copySelectedBtn");
|
||||
const moveBtn = document.getElementById("moveSelectedBtn");
|
||||
const disabled = selectedFiles.length === 0;
|
||||
deleteBtn.disabled = disabled;
|
||||
if (copyBtn) copyBtn.disabled = disabled;
|
||||
if (moveBtn) moveBtn.disabled = disabled;
|
||||
}
|
||||
|
||||
export function toggleAllCheckboxes(source) {
|
||||
@@ -141,7 +55,7 @@ export function toggleAllCheckboxes(source) {
|
||||
|
||||
export function deleteSelectedFiles() {
|
||||
const selectedFiles = Array.from(document.querySelectorAll(".file-checkbox:checked"))
|
||||
.map(checkbox => checkbox.value);
|
||||
.map(checkbox => checkbox.value);
|
||||
if (selectedFiles.length === 0) {
|
||||
alert("No files selected for deletion.");
|
||||
return;
|
||||
@@ -158,10 +72,22 @@ export function deleteSelectedFiles() {
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
loadFileList();
|
||||
loadCopyMoveFolderList();
|
||||
|
||||
const deleteBtn = document.getElementById("deleteSelectedBtn");
|
||||
if (deleteBtn) {
|
||||
deleteBtn.addEventListener("click", deleteSelectedFiles);
|
||||
}
|
||||
|
||||
const copyBtn = document.getElementById("copySelectedBtn");
|
||||
const moveBtn = document.getElementById("moveSelectedBtn");
|
||||
if (copyBtn) {
|
||||
copyBtn.addEventListener("click", copySelectedFiles);
|
||||
}
|
||||
if (moveBtn) {
|
||||
moveBtn.addEventListener("click", moveSelectedFiles);
|
||||
}
|
||||
});
|
||||
|
||||
export function editFile(fileName) {
|
||||
@@ -186,12 +112,12 @@ export function editFile(fileName) {
|
||||
modal.id = "editorContainer";
|
||||
modal.classList.add("modal", "editor-modal");
|
||||
modal.innerHTML = `
|
||||
<h3>Editing: ${fileName}</h3>
|
||||
<textarea id="fileEditor" style="width:100%; height:60%; resize:none;">${content}</textarea>
|
||||
<div style="margin-top:10px; text-align:right;">
|
||||
<button onclick="saveFile('${fileName}')" class="btn btn-primary">Save</button>
|
||||
<button onclick="document.getElementById('editorContainer').remove()" class="btn btn-secondary">Close</button>
|
||||
</div>
|
||||
<h3>Editing: ${fileName}</h3>
|
||||
<textarea id="fileEditor" style="width:100%; height:60%; resize:none;">${content}</textarea>
|
||||
<div style="margin-top:10px; text-align:right;">
|
||||
<button onclick="saveFile('${fileName}')" class="btn btn-primary">Save</button>
|
||||
<button onclick="document.getElementById('editorContainer').remove()" class="btn btn-secondary">Close</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
modal.style.display = "block";
|
||||
@@ -218,11 +144,91 @@ export function saveFile(fileName) {
|
||||
.catch(error => console.error("Error saving file:", error));
|
||||
}
|
||||
|
||||
// To support inline onclick attributes in the generated HTML, attach these functions to window.
|
||||
window.sortFiles = sortFiles;
|
||||
// ===== NEW CODE: Copy & Move Functions =====
|
||||
|
||||
// Copy selected files to a target folder
|
||||
export function copySelectedFiles() {
|
||||
const selectedFiles = Array.from(document.querySelectorAll(".file-checkbox:checked"))
|
||||
.map(checkbox => checkbox.value);
|
||||
const targetFolder = document.getElementById("copyMoveFolderSelect").value;
|
||||
if (selectedFiles.length === 0) {
|
||||
alert("Please select at least one file to copy.");
|
||||
return;
|
||||
}
|
||||
if (!targetFolder) {
|
||||
alert("Please select a target folder.");
|
||||
return;
|
||||
}
|
||||
// Send the correct keys
|
||||
sendRequest("copyFiles.php", "POST", {
|
||||
source: currentFolder,
|
||||
destination: targetFolder,
|
||||
files: selectedFiles
|
||||
})
|
||||
.then(result => {
|
||||
alert(result.success || result.error);
|
||||
loadFileList();
|
||||
})
|
||||
.catch(error => console.error("Error copying files:", error));
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function moveSelectedFiles() {
|
||||
const selectedFiles = Array.from(document.querySelectorAll(".file-checkbox:checked"))
|
||||
.map(checkbox => checkbox.value);
|
||||
const targetFolder = document.getElementById("copyMoveFolderSelect").value;
|
||||
if (selectedFiles.length === 0) {
|
||||
alert("Please select at least one file to move.");
|
||||
return;
|
||||
}
|
||||
if (!targetFolder) {
|
||||
alert("Please select a target folder.");
|
||||
return;
|
||||
}
|
||||
console.log("Payload:", {
|
||||
source: currentFolder,
|
||||
destination: document.getElementById("copyMoveFolderSelect").value,
|
||||
files: selectedFiles
|
||||
});
|
||||
sendRequest("moveFiles.php", "POST", {
|
||||
source: currentFolder,
|
||||
destination: targetFolder,
|
||||
files: selectedFiles
|
||||
})
|
||||
.then(result => {
|
||||
alert(result.success || result.error);
|
||||
loadFileList();
|
||||
})
|
||||
.catch(error => console.error("Error moving files:", error));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Populate the Copy/Move folder dropdown
|
||||
export function loadCopyMoveFolderList() {
|
||||
$.get('getFolderList.php', function (response) {
|
||||
const folderSelect = $('#copyMoveFolderSelect');
|
||||
folderSelect.empty();
|
||||
// Always add a "Root" option as the default.
|
||||
folderSelect.append($('<option>', { value: "root", text: "Root" }));
|
||||
if (Array.isArray(response) && response.length > 0) {
|
||||
response.forEach(function (folder) {
|
||||
folderSelect.append($('<option>', {
|
||||
value: folder,
|
||||
text: folder
|
||||
}));
|
||||
});
|
||||
}
|
||||
}, 'json');
|
||||
}
|
||||
|
||||
// Attach functions to window for inline onclick support
|
||||
window.toggleDeleteButton = toggleDeleteButton;
|
||||
window.toggleAllCheckboxes = toggleAllCheckboxes;
|
||||
window.deleteSelectedFiles = deleteSelectedFiles;
|
||||
window.editFile = editFile;
|
||||
window.saveFile = saveFile;
|
||||
window.loadFileList = loadFileList;
|
||||
window.copySelectedFiles = copySelectedFiles;
|
||||
window.moveSelectedFiles = moveSelectedFiles;
|
||||
|
||||
174
index.html
174
index.html
@@ -2,162 +2,33 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Multi File Upload & Edit</title>
|
||||
<link rel="icon" type="image/svg+xml" href="logo.svg">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="shortcut icon" href="logo.svg">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Multi File Upload Editor</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg">
|
||||
<!-- External CSS -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<!-- Google Fonts and Material Icons -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Scripts (order is important) -->
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||
<script type="module" src="utils.js"></script>
|
||||
<script type="module" src="auth.js"></script>
|
||||
<script type="module" src="upload.js"></script>
|
||||
<script type="module" src="displayFileList.js"></script>
|
||||
|
||||
<style>
|
||||
/* General styles */
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #2196F3;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
min-height: 80px;
|
||||
}
|
||||
.header-left,
|
||||
.header-buttons {
|
||||
width: 150px;
|
||||
}
|
||||
.header-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
.header-title h1 {
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
}
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
.header-buttons button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
border-radius: 50%;
|
||||
transition: background-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
.header-buttons button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.material-icons {
|
||||
font-size: 24px;
|
||||
vertical-align: middle;
|
||||
color: white;
|
||||
}
|
||||
#loginForm {
|
||||
margin: 0 auto;
|
||||
max-width: 400px;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1000;
|
||||
width: 350px;
|
||||
max-width: 90%;
|
||||
height: auto;
|
||||
}
|
||||
.editor-modal {
|
||||
width: 80vw;
|
||||
max-width: 90vw;
|
||||
min-width: 400px;
|
||||
height: 600px;
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
resize: both;
|
||||
}
|
||||
table.table th {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.progress {
|
||||
background-color: #e9ecef;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
height: 20px;
|
||||
}
|
||||
.progress-bar {
|
||||
background-color: #007bff;
|
||||
height: 100%;
|
||||
line-height: 20px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
.card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.actions-cell {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<div class="header-left">
|
||||
<img src="logo.svg" alt="Filing Cabinet Logo" style="height: 60px; width: auto;">
|
||||
<img src="/assets/logo.svg" alt="Filing Cabinet Logo">
|
||||
</div>
|
||||
<div class="header-title">
|
||||
<h1>Multi File Upload & Edit</h1>
|
||||
<h1>Multi File Upload Editor</h1>
|
||||
</div>
|
||||
<div class="header-buttons">
|
||||
<button id="logoutBtn" title="Logout" style="display: none;">
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
</button>
|
||||
<button id="addUserBtn" title="Add User" style="display: none;">
|
||||
<i class="material-icons">person_add</i>
|
||||
</button>
|
||||
<button id="removeUserBtn" title="Remove User" style="display: none;">
|
||||
<i class="material-icons">person_remove</i>
|
||||
</button>
|
||||
<button id="logoutBtn" title="Logout"><i class="material-icons">exit_to_app</i></button>
|
||||
<button id="addUserBtn" title="Add User"><i class="material-icons">person_add</i></button>
|
||||
<button id="removeUserBtn" title="Remove User"><i class="material-icons">person_remove</i></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<!-- Login Form -->
|
||||
<div class="row" id="loginForm">
|
||||
@@ -175,6 +46,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Operations: Upload and Folder Management -->
|
||||
<div id="mainOperations" style="display: none;">
|
||||
<div class="row" id="uploadFolderRow">
|
||||
@@ -199,8 +71,7 @@
|
||||
<div class="card flex-fill">
|
||||
<div class="card-header">Folder Management</div>
|
||||
<div class="card-body">
|
||||
<button id="createFolderBtn" class="btn btn-primary mb-3">Create Folder</button>
|
||||
<div class="form-group d-flex align-items-center">
|
||||
<div class="form-group d-flex align-items-center" style="padding-top:15px;">
|
||||
<select id="folderSelect" class="form-control"></select>
|
||||
<button id="renameFolderBtn" class="btn btn-secondary ml-2" title="Rename Folder">
|
||||
<i class="material-icons">edit</i>
|
||||
@@ -209,17 +80,26 @@
|
||||
<i class="material-icons">delete</i>
|
||||
</button>
|
||||
</div>
|
||||
<button id="createFolderBtn" class="btn btn-primary mt-3">Create Folder</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- File List Section -->
|
||||
<div id="fileListContainer">
|
||||
<h2 id="fileListTitle">Files in (Root)</h2>
|
||||
<button id="deleteSelectedBtn" class="btn btn-danger" style="margin-bottom: 10px; display: none;">Delete Selected</button>
|
||||
<div id="fileListActions" class="file-list-actions">
|
||||
<button id="deleteSelectedBtn" class="btn action-btn" style="display: none;">Delete Selected</button>
|
||||
<button id="copySelectedBtn" class="btn action-btn" style="display: none;" disabled>Copy Selected</button>
|
||||
<button id="moveSelectedBtn" class="btn action-btn" style="display: none;" disabled>Move Selected</button>
|
||||
<select id="copyMoveFolderSelect" class="form-control folder-dropdown"></select>
|
||||
</div>
|
||||
<div id="fileList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add User Modal -->
|
||||
<div id="addUserModal" class="modal">
|
||||
<h3>Create New User</h3>
|
||||
@@ -234,6 +114,7 @@
|
||||
<button id="saveUserBtn" class="btn btn-primary">Save User</button>
|
||||
<button id="cancelUserBtn" class="btn btn-secondary">Cancel</button>
|
||||
</div>
|
||||
|
||||
<!-- Remove User Modal -->
|
||||
<div id="removeUserModal" class="modal">
|
||||
<h3>Remove User</h3>
|
||||
@@ -243,5 +124,12 @@
|
||||
<button id="cancelRemoveUserBtn" class="btn btn-secondary">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript Files -->
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||
<script type="module" src="utils.js"></script>
|
||||
<script type="module" src="auth.js"></script>
|
||||
<script type="module" src="upload.js"></script>
|
||||
<script type="module" src="displayFileList.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
400
styles.css
400
styles.css
@@ -1,20 +1,130 @@
|
||||
/* Container */
|
||||
/* GENERAL STYLES */
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* CONTAINER */
|
||||
.container {
|
||||
margin-top: 20px; /* Increased for better spacing */
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Logout & Add User button container */
|
||||
/* HEADER */
|
||||
header {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
align-items: center !important;
|
||||
justify-content: space-between !important;
|
||||
height: 100px !important;
|
||||
padding: 0 20px !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
background-color: #2196F3 !important;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.header-left img {
|
||||
display: block;
|
||||
height: 60px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
flex: 1 1 auto;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
color: white;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.header-title h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-buttons button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
border-radius: 50%;
|
||||
transition: background-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
|
||||
.header-buttons button:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.material-icons {
|
||||
font-size: 24px;
|
||||
vertical-align: middle;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* LOGIN FORM */
|
||||
#loginForm {
|
||||
margin: 0 auto;
|
||||
max-width: 400px;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* MODALS & EDITOR MODALS */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(75%, 75%); /* centers the modal */
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1000;
|
||||
width: 40vw;
|
||||
max-width: 40vw;
|
||||
height: 600px;
|
||||
max-height: 35vh;
|
||||
}
|
||||
.editor-modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(5%, 10%); /* centers the editor modal */
|
||||
width: 50vw;
|
||||
max-width: 90vw;
|
||||
min-width: 400px;
|
||||
height: 600px;
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
resize: both;
|
||||
}
|
||||
|
||||
/* LOGOUT & USER BUTTON CONTAINER */
|
||||
.logout-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end; /* keep buttons right-aligned */
|
||||
gap: 5px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
/* Progress bar container style */
|
||||
/* UPLOAD PROGRESS */
|
||||
#uploadProgressContainer ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
@@ -24,35 +134,24 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
/* Allow wrapping for long file names */
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Force file preview container to be 32x32 pixels and adjust vertical position */
|
||||
#uploadProgressContainer .file-preview {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
/* Use transform to nudge down the preview */
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
/* Ensure that the image inside the preview fills the container */
|
||||
#uploadProgressContainer .file-preview img {
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* File name styling */
|
||||
#uploadProgressContainer .file-name {
|
||||
margin-right: 20px;
|
||||
flex-grow: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Progress bar container style */
|
||||
#uploadProgressContainer .progress {
|
||||
background-color: #e9ecef;
|
||||
border-radius: 5px;
|
||||
@@ -60,172 +159,187 @@
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
height: 24px;
|
||||
width: 250px; /* Increased width */
|
||||
/* No extra left margin so it sits right next to file name */
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
/* Progress bar element style */
|
||||
#uploadProgressContainer .progress-bar {
|
||||
background-color: #007bff;
|
||||
height: 100%;
|
||||
line-height: 24px;
|
||||
color: #000; /* black text for legibility */
|
||||
color: #000;
|
||||
text-align: center;
|
||||
transition: width 0.4s ease;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Ensure the upload progress container has some top margin */
|
||||
#uploadProgressContainer {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* On small screens, move buttons below title */
|
||||
/* RESPONSIVE (small screens) */
|
||||
@media (max-width: 768px) {
|
||||
.logout-container {
|
||||
position: static;
|
||||
align-items: flex-end; /* Right-align buttons */
|
||||
text-align: right;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.logout-container button {
|
||||
width: auto;
|
||||
min-width: 120px; /* Ensures buttons don't become huge */
|
||||
}
|
||||
.logout-container {
|
||||
position: static;
|
||||
align-items: flex-end;
|
||||
text-align: right;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.logout-container button {
|
||||
width: auto;
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal styling */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border: 1px solid #ccc; /* Lighter border for a modern look */
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
z-index: 1000;
|
||||
width: 280px; /* slightly narrower */
|
||||
height: auto; /* Let height adjust to content */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
/* BUTTON STYLES (MATERIAL THEME) */
|
||||
.btn {
|
||||
font-size: 0.9rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Editor modal: for edit popup with rounded corners */
|
||||
.editor-modal {
|
||||
width: 80vw;
|
||||
max-width: 90vw;
|
||||
min-width: 400px;
|
||||
height: 400px;
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
resize: both;
|
||||
.btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Header styling */
|
||||
header {
|
||||
/* File list action buttons (for Delete, Copy, Move) */
|
||||
.file-list-actions button {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
min-height: 80px;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.file-list-actions button:hover {
|
||||
background-color: #1976D2;
|
||||
}
|
||||
|
||||
/* Material icons */
|
||||
.material-icons {
|
||||
font-size: 24px;
|
||||
vertical-align: middle;
|
||||
#deleteSelectedBtn {
|
||||
background-color: #f44336; /* Material red */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
/* Header buttons container */
|
||||
.header-buttons {
|
||||
#deleteSelectedBtn:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
#copySelectedBtn {
|
||||
background-color: #9E9E9E; /* Material grey */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
#copySelectedBtn:hover {
|
||||
background-color: #757575;
|
||||
}
|
||||
|
||||
#moveSelectedBtn {
|
||||
background-color: #ff9800; /* Material orange */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
#moveSelectedBtn:hover {
|
||||
background-color: #fb8c00;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Material green style for Edit button in file list */
|
||||
#fileList button.edit-btn {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#fileList button.edit-btn:hover {
|
||||
background-color: #43A047;
|
||||
}
|
||||
|
||||
/* FILE LIST ACTIONS CONTAINER */
|
||||
.file-list-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
align-items: center;
|
||||
max-width: 800px;
|
||||
}
|
||||
.folder-dropdown {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
/* Upload and Choose File button styles */
|
||||
.btn-upload {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
/* FILE LIST TABLE */
|
||||
#fileList table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.btn-upload:disabled {
|
||||
background-color: gray;
|
||||
#fileList table th,
|
||||
#fileList table td {
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
border: none; /* Remove table borders */
|
||||
}
|
||||
.btn-choose-file {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
#fileList table tr:nth-child(even) {
|
||||
background-color: transparent; /* Remove alternating grey rows */
|
||||
}
|
||||
#fileList table tr:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
/* File list and progress bar */
|
||||
.file-list {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.progress {
|
||||
margin-top: 10px;
|
||||
height: 20px; /* Narrow progress bar */
|
||||
width: 100%;
|
||||
}
|
||||
.progress-bar {
|
||||
height: 100%; /* Fill the entire height */
|
||||
}
|
||||
|
||||
/* Table styling */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table, th, td {
|
||||
border: 1px solid #ccc; /* Lighter border color */
|
||||
}
|
||||
th, td {
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
tr:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
/* Headings and form labels */
|
||||
/* HEADINGS & FORM LABELS */
|
||||
h2 {
|
||||
font-size: 2em; /* Larger heading for better emphasis */
|
||||
font-size: 2em;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 10px; /* Increased spacing */
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 0.9rem; /* Rem unit for scalability */
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Button font size */
|
||||
.btn {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Utility class for aligning items */
|
||||
/* UTILITY CLASSES */
|
||||
.align-items-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Table header button style (if using buttons inside headers) */
|
||||
.table th button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Initially hide login and upload forms */
|
||||
/* INITIAL HIDE FORMS */
|
||||
#loginForm, #uploadForm {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* Remove bottom margin from the form group */
|
||||
.card-body .form-group {
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
|
||||
/* Force a small top margin for the Create Folder button */
|
||||
#createFolderBtn {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
#fileListContainer {
|
||||
margin-top: 40px !important;
|
||||
}
|
||||
|
||||
320
utils.js
320
utils.js
@@ -1,6 +1,8 @@
|
||||
// =======================
|
||||
// Utility Functions
|
||||
// =======================
|
||||
let fileData = []; // will store the fetched file data
|
||||
let sortOrder = { column: "uploaded", ascending: true };
|
||||
|
||||
/**
|
||||
* Sends an AJAX request using the Fetch API.
|
||||
@@ -61,8 +63,9 @@ let setupMode = false;
|
||||
* @param {string} fileName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
||||
function canEditFile(fileName) {
|
||||
const allowedExtensions = ["txt", "html", "htm", "php", "css", "js", "json", "xml", "md", "py"];
|
||||
const allowedExtensions = ["txt", "html", "htm", "php", "css", "js", "json", "xml", "md", "py", "ini", "csv", "log", "conf", "config", "bat", "rtf", "doc", "docx"];
|
||||
const parts = fileName.split('.');
|
||||
if (parts.length < 2) return false;
|
||||
const ext = parts.pop().toLowerCase();
|
||||
@@ -406,6 +409,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
// -----------------------
|
||||
// File List Management
|
||||
// -----------------------
|
||||
|
||||
// Load the file list for a given folder (defaults to currentFolder or "root")
|
||||
function loadFileList(folderParam) {
|
||||
const folder = folderParam || currentFolder || "root";
|
||||
fetch("getFileList.php?folder=" + encodeURIComponent(folder))
|
||||
@@ -414,102 +419,145 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
const fileListContainer = document.getElementById("fileList");
|
||||
fileListContainer.innerHTML = "";
|
||||
if (data.files && data.files.length > 0) {
|
||||
const table = document.createElement("table");
|
||||
table.classList.add("table");
|
||||
const thead = document.createElement("thead");
|
||||
const headerRow = document.createElement("tr");
|
||||
// Add select-all checkbox in header.
|
||||
const selectTh = document.createElement("th");
|
||||
const selectAll = document.createElement("input");
|
||||
selectAll.type = "checkbox";
|
||||
selectAll.id = "selectAllFiles";
|
||||
selectAll.addEventListener("change", function () {
|
||||
const checkboxes = document.querySelectorAll(".file-checkbox");
|
||||
checkboxes.forEach(chk => chk.checked = this.checked);
|
||||
updateDeleteSelectedVisibility();
|
||||
});
|
||||
selectTh.appendChild(selectAll);
|
||||
headerRow.appendChild(selectTh);
|
||||
["Name", "Modified", "Uploaded", "Size", "Uploader", "Actions"].forEach(headerText => {
|
||||
const th = document.createElement("th");
|
||||
th.textContent = headerText;
|
||||
headerRow.appendChild(th);
|
||||
});
|
||||
thead.appendChild(headerRow);
|
||||
table.appendChild(thead);
|
||||
const tbody = document.createElement("tbody");
|
||||
const folderPath = (folder === "root") ? "uploads/" : "uploads/" + encodeURIComponent(folder) + "/";
|
||||
data.files.forEach(file => {
|
||||
const row = document.createElement("tr");
|
||||
const checkboxTd = document.createElement("td");
|
||||
const checkbox = document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.className = "file-checkbox";
|
||||
checkbox.value = file.name;
|
||||
checkbox.addEventListener("change", updateDeleteSelectedVisibility);
|
||||
checkboxTd.appendChild(checkbox);
|
||||
row.appendChild(checkboxTd);
|
||||
const nameTd = document.createElement("td");
|
||||
nameTd.textContent = file.name;
|
||||
row.appendChild(nameTd);
|
||||
const modifiedTd = document.createElement("td");
|
||||
modifiedTd.textContent = file.modified;
|
||||
row.appendChild(modifiedTd);
|
||||
const uploadedTd = document.createElement("td");
|
||||
uploadedTd.textContent = file.uploaded;
|
||||
row.appendChild(uploadedTd);
|
||||
const sizeTd = document.createElement("td");
|
||||
sizeTd.textContent = file.size;
|
||||
row.appendChild(sizeTd);
|
||||
const uploaderTd = document.createElement("td");
|
||||
uploaderTd.textContent = file.uploader;
|
||||
row.appendChild(uploaderTd);
|
||||
const actionsTd = document.createElement("td");
|
||||
actionsTd.className = "actions-cell";
|
||||
const downloadButton = document.createElement("a");
|
||||
downloadButton.className = "btn btn-sm btn-success";
|
||||
downloadButton.href = folderPath + encodeURIComponent(file.name);
|
||||
downloadButton.download = file.name;
|
||||
downloadButton.textContent = "Download";
|
||||
actionsTd.appendChild(downloadButton);
|
||||
if (canEditFile(file.name)) {
|
||||
const editButton = document.createElement("button");
|
||||
editButton.className = "btn btn-sm btn-primary ml-2";
|
||||
editButton.textContent = "Edit";
|
||||
editButton.addEventListener("click", function () {
|
||||
editFile(file.name, currentFolder);
|
||||
});
|
||||
actionsTd.appendChild(editButton);
|
||||
}
|
||||
row.appendChild(actionsTd);
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
table.appendChild(tbody);
|
||||
fileListContainer.appendChild(table);
|
||||
updateDeleteSelectedVisibility();
|
||||
// Save the file list globally for sorting
|
||||
fileData = data.files;
|
||||
// Render the table initially using the current sortOrder
|
||||
renderFileTable(folder);
|
||||
} else {
|
||||
fileListContainer.textContent = "No files found.";
|
||||
document.getElementById("deleteSelectedBtn").style.display = "none";
|
||||
document.getElementById("copySelectedBtn").style.display = "none";
|
||||
document.getElementById("moveSelectedBtn").style.display = "none";
|
||||
}
|
||||
})
|
||||
.catch(error => console.error("Error loading file list:", error));
|
||||
}
|
||||
|
||||
function updateDeleteSelectedVisibility() {
|
||||
const checkboxes = document.querySelectorAll(".file-checkbox");
|
||||
const deleteBtn = document.getElementById("deleteSelectedBtn");
|
||||
if (checkboxes.length > 0) {
|
||||
deleteBtn.style.display = "inline-block";
|
||||
let anyChecked = false;
|
||||
checkboxes.forEach(chk => {
|
||||
if (chk.checked) anyChecked = true;
|
||||
function renderFileTable(folder) {
|
||||
const fileListContainer = document.getElementById("fileList");
|
||||
const folderPath = (folder === "root") ? "uploads/" : "uploads/" + encodeURIComponent(folder) + "/";
|
||||
let tableHTML = `<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" id="selectAll" onclick="toggleAllCheckboxes(this)"></th>
|
||||
<th data-column="name" style="cursor:pointer; text-decoration: underline; white-space: nowrap;">
|
||||
File Name ${sortOrder.column === "name" ? (sortOrder.ascending ? "▲" : "▼") : ""}
|
||||
</th>
|
||||
<th data-column="modified" style="cursor:pointer; text-decoration: underline; white-space: nowrap;">
|
||||
Date Modified ${sortOrder.column === "modified" ? (sortOrder.ascending ? "▲" : "▼") : ""}
|
||||
</th>
|
||||
<th data-column="uploaded" style="cursor:pointer; text-decoration: underline; white-space: nowrap;">
|
||||
Upload Date ${sortOrder.column === "uploaded" ? (sortOrder.ascending ? "▲" : "▼") : ""}
|
||||
</th>
|
||||
<th data-column="size" style="cursor:pointer; text-decoration: underline; white-space: nowrap;">
|
||||
File Size ${sortOrder.column === "size" ? (sortOrder.ascending ? "▲" : "▼") : ""}
|
||||
</th>
|
||||
<th data-column="uploader" style="cursor:pointer; text-decoration: underline; white-space: nowrap;">
|
||||
Uploader ${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""}
|
||||
</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>`;
|
||||
|
||||
fileData.forEach(file => {
|
||||
// Determine if file is editable via your canEditFile() helper
|
||||
const isEditable = canEditFile(file.name);
|
||||
tableHTML += `<tr>
|
||||
<td><input type="checkbox" class="file-checkbox" value="${file.name}" onclick="toggleDeleteButton()"></td>
|
||||
<td>${file.name}</td>
|
||||
<td style="white-space: nowrap;">${file.modified}</td>
|
||||
<td style="white-space: nowrap;">${file.uploaded}</td>
|
||||
<td style="white-space: nowrap;">${file.size}</td>
|
||||
<td style="white-space: nowrap;">${file.uploader || "Unknown"}</td>
|
||||
<td>
|
||||
<div style="display: inline-flex; align-items: center; gap: 5px; flex-wrap: nowrap;">
|
||||
<a class="btn btn-sm btn-success" href="${folderPath + encodeURIComponent(file.name)}" download>Download</a>
|
||||
${isEditable ? `<button class="btn btn-sm btn-primary ml-2" onclick="editFile('${file.name}', '${folder}')">Edit</button>` : ""}
|
||||
</div>
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
tableHTML += `</tbody></table>`;
|
||||
fileListContainer.innerHTML = tableHTML;
|
||||
|
||||
// Attach click event listeners to header cells for sorting
|
||||
const headerCells = document.querySelectorAll("table.table thead th[data-column]");
|
||||
headerCells.forEach(cell => {
|
||||
cell.addEventListener("click", function () {
|
||||
const column = this.getAttribute("data-column");
|
||||
sortFiles(column, folder);
|
||||
});
|
||||
deleteBtn.disabled = !anyChecked;
|
||||
});
|
||||
|
||||
// Show or hide action buttons based on whether files exist
|
||||
const deleteBtn = document.getElementById("deleteSelectedBtn");
|
||||
const copyBtn = document.getElementById("copySelectedBtn");
|
||||
const moveBtn = document.getElementById("moveSelectedBtn");
|
||||
if (fileData.length > 0) {
|
||||
deleteBtn.style.display = "block";
|
||||
copyBtn.style.display = "block";
|
||||
moveBtn.style.display = "block";
|
||||
} else {
|
||||
deleteBtn.style.display = "none";
|
||||
copyBtn.style.display = "none";
|
||||
moveBtn.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function sortFiles(column, folder) {
|
||||
// Toggle sort direction if the same column is clicked; otherwise, sort ascending
|
||||
if (sortOrder.column === column) {
|
||||
sortOrder.ascending = !sortOrder.ascending;
|
||||
} else {
|
||||
sortOrder.column = column;
|
||||
sortOrder.ascending = true;
|
||||
}
|
||||
fileData.sort((a, b) => {
|
||||
let valA = a[column] || "";
|
||||
let valB = b[column] || "";
|
||||
// If sorting by date, convert to timestamp
|
||||
if (column === "modified" || column === "uploaded") {
|
||||
valA = new Date(valA).getTime();
|
||||
valB = new Date(valB).getTime();
|
||||
} else if (typeof valA === "string") {
|
||||
valA = valA.toLowerCase();
|
||||
valB = valB.toLowerCase();
|
||||
}
|
||||
if (valA < valB) return sortOrder.ascending ? -1 : 1;
|
||||
if (valA > valB) return sortOrder.ascending ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
// Re-render the table after sorting
|
||||
renderFileTable(folder);
|
||||
}
|
||||
|
||||
|
||||
// Update the visibility and enabled state of the Delete, Copy, and Move buttons
|
||||
function updateDeleteSelectedVisibility() {
|
||||
const checkboxes = document.querySelectorAll(".file-checkbox");
|
||||
const deleteBtn = document.getElementById("deleteSelectedBtn");
|
||||
const copyBtn = document.getElementById("copySelectedBtn");
|
||||
const moveBtn = document.getElementById("moveSelectedBtn");
|
||||
if (checkboxes.length > 0) {
|
||||
// Show all three action buttons
|
||||
deleteBtn.style.display = "inline-block";
|
||||
copyBtn.style.display = "inline-block";
|
||||
moveBtn.style.display = "inline-block";
|
||||
let anyChecked = false;
|
||||
checkboxes.forEach(chk => { if (chk.checked) anyChecked = true; });
|
||||
deleteBtn.disabled = !anyChecked;
|
||||
copyBtn.disabled = !anyChecked;
|
||||
moveBtn.disabled = !anyChecked;
|
||||
} else {
|
||||
deleteBtn.style.display = "none";
|
||||
copyBtn.style.display = "none";
|
||||
moveBtn.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// Delete Selected Files handler (existing)
|
||||
function handleDeleteSelected(e) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
@@ -539,10 +587,116 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
.catch(error => console.error("Error deleting files:", error));
|
||||
}
|
||||
|
||||
// NEW: Handle Copy Selected Files
|
||||
function handleCopySelected(e) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
const checkboxes = document.querySelectorAll(".file-checkbox:checked");
|
||||
if (checkboxes.length === 0) {
|
||||
alert("No files selected for copying.");
|
||||
return;
|
||||
}
|
||||
const targetFolder = document.getElementById("copyMoveFolderSelect").value;
|
||||
if (!targetFolder) {
|
||||
alert("Please select a target folder for copying.");
|
||||
return;
|
||||
}
|
||||
const filesToCopy = Array.from(checkboxes).map(chk => chk.value);
|
||||
fetch("copyFiles.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ source: currentFolder, files: filesToCopy, destination: targetFolder })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert("Selected files copied successfully!");
|
||||
loadFileList(currentFolder);
|
||||
} else {
|
||||
alert("Error: " + (data.error || "Could not copy files"));
|
||||
}
|
||||
})
|
||||
.catch(error => console.error("Error copying files:", error));
|
||||
}
|
||||
|
||||
// NEW: Handle Move Selected Files
|
||||
function handleMoveSelected(e) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
const checkboxes = document.querySelectorAll(".file-checkbox:checked");
|
||||
if (checkboxes.length === 0) {
|
||||
alert("No files selected for moving.");
|
||||
return;
|
||||
}
|
||||
const targetFolder = document.getElementById("copyMoveFolderSelect").value;
|
||||
if (!targetFolder) {
|
||||
alert("Please select a target folder for moving.");
|
||||
return;
|
||||
}
|
||||
const filesToMove = Array.from(checkboxes).map(chk => chk.value);
|
||||
fetch("moveFiles.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ source: currentFolder, files: filesToMove, destination: targetFolder })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert("Selected files moved successfully!");
|
||||
loadFileList(currentFolder);
|
||||
} else {
|
||||
alert("Error: " + (data.error || "Could not move files"));
|
||||
}
|
||||
})
|
||||
.catch(error => console.error("Error moving files:", error));
|
||||
}
|
||||
|
||||
// Attach event listeners to the action buttons.
|
||||
// Use cloneNode() to remove any previously attached listeners.
|
||||
const deleteSelectedBtn = document.getElementById("deleteSelectedBtn");
|
||||
deleteSelectedBtn.replaceWith(deleteSelectedBtn.cloneNode(true));
|
||||
document.getElementById("deleteSelectedBtn").addEventListener("click", handleDeleteSelected);
|
||||
|
||||
const copySelectedBtn = document.getElementById("copySelectedBtn");
|
||||
copySelectedBtn.replaceWith(copySelectedBtn.cloneNode(true));
|
||||
document.getElementById("copySelectedBtn").addEventListener("click", handleCopySelected);
|
||||
|
||||
const moveSelectedBtn = document.getElementById("moveSelectedBtn");
|
||||
moveSelectedBtn.replaceWith(moveSelectedBtn.cloneNode(true));
|
||||
document.getElementById("moveSelectedBtn").addEventListener("click", handleMoveSelected);
|
||||
|
||||
// NEW: Load the folder list into the copy/move dropdown
|
||||
function loadCopyMoveFolderList() {
|
||||
fetch("getFolderList.php")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const folderSelect = document.getElementById("copyMoveFolderSelect");
|
||||
folderSelect.innerHTML = "";
|
||||
// Optionally, add a default prompt option
|
||||
const defaultOption = document.createElement("option");
|
||||
defaultOption.value = "";
|
||||
defaultOption.textContent = "Select folder";
|
||||
folderSelect.appendChild(defaultOption);
|
||||
if (data && data.length > 0) {
|
||||
data.forEach(folder => {
|
||||
const option = document.createElement("option");
|
||||
option.value = folder;
|
||||
option.textContent = folder;
|
||||
folderSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => console.error("Error loading folder list:", error));
|
||||
}
|
||||
|
||||
// On DOMContentLoaded, load the file list and the folder dropdown.
|
||||
// Ensure currentFolder is defined globally (defaulting to "root" if not).
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
currentFolder = currentFolder || "root";
|
||||
loadFileList(currentFolder);
|
||||
loadCopyMoveFolderList();
|
||||
});
|
||||
|
||||
// -----------------------
|
||||
// File Editing Functions
|
||||
// -----------------------
|
||||
|
||||
Reference in New Issue
Block a user