diff --git a/domUtils.js b/domUtils.js
index e754faf..e3e89c0 100644
--- a/domUtils.js
+++ b/domUtils.js
@@ -1,5 +1,6 @@
// domUtils.js
+// Basic DOM Helpers
export function toggleVisibility(elementId, shouldShow) {
const element = document.getElementById(elementId);
if (element) {
@@ -18,16 +19,14 @@ export function escapeHTML(str) {
.replace(/'/g, "'");
}
-// Toggle all checkboxes (assumes checkboxes have class 'file-checkbox')
export function toggleAllCheckboxes(masterCheckbox) {
const checkboxes = document.querySelectorAll(".file-checkbox");
checkboxes.forEach(chk => {
chk.checked = masterCheckbox.checked;
});
- updateFileActionButtons(); // call the updated function
+ updateFileActionButtons(); // update buttons based on current selection
}
-// This updateFileActionButtons function checks for checkboxes inside the file list container.
export function updateFileActionButtons() {
const fileListContainer = document.getElementById("fileList");
const fileCheckboxes = document.querySelectorAll("#fileList .file-checkbox");
@@ -36,31 +35,28 @@ export function updateFileActionButtons() {
const moveBtn = document.getElementById("moveSelectedBtn");
const deleteBtn = document.getElementById("deleteSelectedBtn");
const zipBtn = document.getElementById("downloadZipBtn");
-
- // Hide the buttons and dropdown if no files exist.
+
if (fileCheckboxes.length === 0) {
- copyBtn.style.display = "none";
- moveBtn.style.display = "none";
- deleteBtn.style.display = "none";
- zipBtn.style.display = "none";
+ if (copyBtn) copyBtn.style.display = "none";
+ if (moveBtn) moveBtn.style.display = "none";
+ if (deleteBtn) deleteBtn.style.display = "none";
+ if (zipBtn) zipBtn.style.display = "none";
} else {
- // Otherwise, show the buttons and dropdown.
- copyBtn.style.display = "inline-block";
- moveBtn.style.display = "inline-block";
- deleteBtn.style.display = "inline-block";
- zipBtn.style.display = "inline-block";
-
- // Enable the buttons if at least one file is selected; otherwise disable.
+ if (copyBtn) copyBtn.style.display = "inline-block";
+ if (moveBtn) moveBtn.style.display = "inline-block";
+ if (deleteBtn) deleteBtn.style.display = "inline-block";
+ if (zipBtn) zipBtn.style.display = "inline-block";
+
if (selectedCheckboxes.length > 0) {
- copyBtn.disabled = false;
- moveBtn.disabled = false;
- deleteBtn.disabled = false;
- zipBtn.disabled = false;
+ if (copyBtn) copyBtn.disabled = false;
+ if (moveBtn) moveBtn.disabled = false;
+ if (deleteBtn) deleteBtn.disabled = false;
+ if (zipBtn) zipBtn.disabled = false;
} else {
- copyBtn.disabled = true;
- moveBtn.disabled = true;
- deleteBtn.disabled = true;
- zipBtn.disabled = true;
+ if (copyBtn) copyBtn.disabled = true;
+ if (moveBtn) moveBtn.disabled = true;
+ if (deleteBtn) deleteBtn.disabled = true;
+ if (zipBtn) zipBtn.disabled = true;
}
}
}
@@ -73,13 +69,226 @@ export function showToast(message, duration = 3000) {
}
toast.textContent = message;
toast.style.display = "block";
- // Force reflow so the transition works.
+ // Force reflow for transition effect.
void toast.offsetWidth;
toast.classList.add("show");
setTimeout(() => {
toast.classList.remove("show");
setTimeout(() => {
toast.style.display = "none";
- }, 500); // Wait for the opacity transition to finish.
+ }, 500);
}, duration);
+}
+
+// --- DOM Building Functions for File Table ---
+
+export function buildSearchAndPaginationControls({ currentPage, totalPages, searchTerm }) {
+ const safeSearchTerm = escapeHTML(searchTerm);
+ return `
+
+
+
+
+
+ Page ${currentPage} of ${totalPages || 1}
+
+
+
+
+ `;
+}
+
+export function buildFileTableHeader(sortOrder) {
+ return `
+
+
+
+ |
+ File Name ${sortOrder.column === "name" ? (sortOrder.ascending ? "▲" : "▼") : ""} |
+ Date Modified ${sortOrder.column === "modified" ? (sortOrder.ascending ? "▲" : "▼") : ""} |
+ Upload Date ${sortOrder.column === "uploaded" ? (sortOrder.ascending ? "▲" : "▼") : ""} |
+ File Size ${sortOrder.column === "size" ? (sortOrder.ascending ? "▲" : "▼") : ""} |
+ Uploader ${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""} |
+ Actions |
+
+
+ `;
+}
+
+export function buildFileTableRow(file, folderPath) {
+ const safeFileName = escapeHTML(file.name);
+ const safeModified = escapeHTML(file.modified);
+ const safeUploaded = escapeHTML(file.uploaded);
+ const safeSize = escapeHTML(file.size);
+ const safeUploader = escapeHTML(file.uploader || "Unknown");
+
+ let previewButton = "";
+ if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic|pdf|mp4|webm|mov|ogg)$/i.test(file.name)) {
+ let previewIcon = "";
+ if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic)$/i.test(file.name)) {
+ previewIcon = `image`;
+ } else if (/\.(mp4|webm|mov|ogg)$/i.test(file.name)) {
+ previewIcon = `videocam`;
+ } else if (/\.pdf$/i.test(file.name)) {
+ previewIcon = `picture_as_pdf`;
+ }
+ previewButton = ``;
+ }
+
+ return `
+
+ |
+
+ |
+ ${safeFileName} |
+ ${safeModified} |
+ ${safeUploaded} |
+ ${safeSize} |
+ ${safeUploader} |
+
+
+ |
+
+ `;
+}
+
+export function buildBottomControls(itemsPerPageSetting) {
+ return `
+
+
+
+ items per page
+
+ `;
+}
+
+// --- Global Helper Functions ---
+
+export function debounce(func, wait) {
+ let timeout;
+ return function (...args) {
+ clearTimeout(timeout);
+ timeout = setTimeout(() => func.apply(this, args), wait);
+ };
+}
+
+export function updateRowHighlight(checkbox) {
+ const row = checkbox.closest('tr');
+ if (!row) return;
+ if (checkbox.checked) {
+ row.classList.add('row-selected');
+ } else {
+ row.classList.remove('row-selected');
+ }
+}
+
+export function toggleRowSelection(event, fileName) {
+ const targetTag = event.target.tagName.toLowerCase();
+ if (targetTag === 'a' || targetTag === 'button' || targetTag === 'input') {
+ return;
+ }
+ const row = event.currentTarget;
+ const checkbox = row.querySelector('.file-checkbox');
+ if (!checkbox) return;
+ checkbox.checked = !checkbox.checked;
+ updateRowHighlight(checkbox);
+ updateFileActionButtons();
+}
+
+export function previewFile(fileUrl, fileName) {
+ let modal = document.getElementById("filePreviewModal");
+ if (!modal) {
+ modal = document.createElement("div");
+ modal.id = "filePreviewModal";
+ Object.assign(modal.style, {
+ display: "none",
+ position: "fixed",
+ top: "0",
+ left: "0",
+ width: "100vw",
+ height: "100vh",
+ backgroundColor: "rgba(0,0,0,0.7)",
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+ zIndex: "1000"
+ });
+ modal.innerHTML = `
+ `;
+ document.body.appendChild(modal);
+
+ document.getElementById("closeFileModal").addEventListener("click", function () {
+ const video = modal.querySelector("video");
+ if (video) {
+ video.pause();
+ video.currentTime = 0;
+ }
+ modal.style.display = "none";
+ });
+
+ modal.addEventListener("click", function (e) {
+ if (e.target === modal) {
+ const video = modal.querySelector("video");
+ if (video) {
+ video.pause();
+ video.currentTime = 0;
+ }
+ modal.style.display = "none";
+ }
+ });
+ }
+
+ modal.querySelector("h4").textContent = fileName;
+ const container = modal.querySelector(".file-preview-container");
+ container.innerHTML = "";
+
+ const extension = fileName.split('.').pop().toLowerCase();
+
+ if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic)$/i.test(fileName)) {
+ const img = document.createElement("img");
+ img.src = fileUrl;
+ img.className = "image-modal-img";
+ container.appendChild(img);
+ } else if (extension === "pdf") {
+ const embed = document.createElement("embed");
+ const separator = fileUrl.indexOf('?') === -1 ? '?' : '&';
+ embed.src = fileUrl + separator + 't=' + new Date().getTime();
+ embed.type = "application/pdf";
+ embed.style.width = "80vw";
+ embed.style.height = "80vh";
+ embed.style.border = "none";
+ container.appendChild(embed);
+ } else if (/\.(mp4|webm|mov|ogg)$/i.test(fileName)) {
+ const video = document.createElement("video");
+ video.src = fileUrl;
+ video.controls = true;
+ video.className = "image-modal-img";
+ container.appendChild(video);
+ } else {
+ container.textContent = "Preview not available for this file type.";
+ }
+
+ modal.style.display = "flex";
}
\ No newline at end of file
diff --git a/fileManager.js b/fileManager.js
index d871503..04ddeae 100644
--- a/fileManager.js
+++ b/fileManager.js
@@ -1,19 +1,180 @@
// fileManager.js
-import { escapeHTML, updateFileActionButtons, showToast } from './domUtils.js';
-import { formatFolderName } from './folderManager.js';
+
+import {
+ escapeHTML,
+ debounce,
+ buildSearchAndPaginationControls,
+ buildFileTableHeader,
+ buildFileTableRow,
+ buildBottomControls,
+ updateFileActionButtons,
+ showToast,
+ updateRowHighlight,
+ toggleRowSelection,
+ previewFile
+} from './domUtils.js';
export let fileData = [];
export let sortOrder = { column: "uploaded", ascending: true };
-// Global pagination defaults
window.itemsPerPage = window.itemsPerPage || 10;
window.currentPage = window.currentPage || 1;
-// -------------------------------
-// Helper Functions
-// -------------------------------
+// --- Define formatFolderName ---
+// This helper formats folder names for display. Adjust as needed.
+function formatFolderName(folder) {
+ // Example: If folder is "root", return "(Root)"
+ if (folder === "root") return "(Root)";
+ // Replace underscores/dashes with spaces and capitalize each word.
+ return folder
+ .replace(/[_-]+/g, " ")
+ .replace(/\b\w/g, char => char.toUpperCase());
+}
+
+// Expose DOM helper functions for inline handlers.
+window.toggleRowSelection = toggleRowSelection;
+window.updateRowHighlight = updateRowHighlight;
+window.previewFile = previewFile;
+
+export function loadFileList(folderParam) {
+ const folder = folderParam || "root";
+ const fileListContainer = document.getElementById("fileList");
+
+ fileListContainer.style.visibility = "hidden";
+ fileListContainer.innerHTML = "Loading files...
";
+
+ return fetch("getFileList.php?folder=" + encodeURIComponent(folder) + "&recursive=1&t=" + new Date().getTime())
+ .then(response => response.json())
+ .then(data => {
+ fileListContainer.innerHTML = "";
+ if (data.files && data.files.length > 0) {
+ data.files = data.files.map(file => {
+ file.fullName = (file.path || file.name).trim().toLowerCase();
+ file.editable = canEditFile(file.name);
+ file.folder = folder;
+ return file;
+ });
+ fileData = data.files;
+ renderFileTable(folder);
+ } else {
+ fileListContainer.textContent = "No files found.";
+ updateFileActionButtons();
+ }
+ return data.files || [];
+ })
+ .catch(error => {
+ console.error("Error loading file list:", error);
+ fileListContainer.textContent = "Error loading files.";
+ return [];
+ })
+ .finally(() => {
+ fileListContainer.style.visibility = "visible";
+ });
+}
+
+export function renderFileTable(folder) {
+ const fileListContainer = document.getElementById("fileList");
+ const searchTerm = window.currentSearchTerm || "";
+ const itemsPerPageSetting = parseInt(localStorage.getItem('itemsPerPage') || '10', 10);
+ let currentPage = window.currentPage || 1;
+
+ const filteredFiles = fileData.filter(file =>
+ file.name.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ const totalFiles = filteredFiles.length;
+ const totalPages = Math.ceil(totalFiles / itemsPerPageSetting);
+
+ if (currentPage > totalPages) {
+ currentPage = totalPages > 0 ? totalPages : 1;
+ window.currentPage = currentPage;
+ }
+
+ const folderPath = (folder === "root")
+ ? "uploads/"
+ : "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
+
+ const topControlsHTML = buildSearchAndPaginationControls({
+ currentPage,
+ totalPages,
+ searchTerm
+ });
+
+ const headerHTML = buildFileTableHeader(sortOrder);
+
+ const startIndex = (currentPage - 1) * itemsPerPageSetting;
+ const endIndex = Math.min(startIndex + itemsPerPageSetting, totalFiles);
+
+ let rowsHTML = "";
+ if (totalFiles > 0) {
+ filteredFiles.slice(startIndex, endIndex).forEach(file => {
+ rowsHTML += buildFileTableRow(file, folderPath);
+ });
+ } else {
+ rowsHTML += `| No files found. |
`;
+ }
+ rowsHTML += "
";
+
+ const bottomControlsHTML = buildBottomControls(itemsPerPageSetting);
+
+ fileListContainer.innerHTML = topControlsHTML + headerHTML + rowsHTML + bottomControlsHTML;
+
+ const newSearchInput = document.getElementById("searchInput");
+ if (newSearchInput) {
+ newSearchInput.addEventListener("input", debounce(function () {
+ window.currentSearchTerm = newSearchInput.value;
+ window.currentPage = 1;
+ renderFileTable(folder);
+ setTimeout(() => {
+ newSearchInput.focus();
+ newSearchInput.setSelectionRange(newSearchInput.value.length, newSearchInput.value.length);
+ }, 0);
+ }, 300));
+ }
+
+ document.querySelectorAll("table.table thead th[data-column]").forEach(cell => {
+ cell.addEventListener("click", function () {
+ const column = this.getAttribute("data-column");
+ sortFiles(column, folder);
+ });
+ });
+
+ document.querySelectorAll('#fileList .file-checkbox').forEach(checkbox => {
+ checkbox.addEventListener('change', function (e) {
+ updateRowHighlight(e.target);
+ updateFileActionButtons();
+ });
+ });
+
+ updateFileActionButtons();
+}
+
+export function sortFiles(column, folder) {
+ 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 (column === "modified" || column === "uploaded") {
+ const parsedA = parseCustomDate(valA);
+ const parsedB = parseCustomDate(valB);
+ valA = parsedA;
+ valB = parsedB;
+ } 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(folder);
+}
-// Parse date strings in "m/d/y h:iA" format into a timestamp.
function parseCustomDate(dateStr) {
dateStr = dateStr.replace(/\s+/g, " ").trim();
const parts = dateStr.split(" ");
@@ -49,7 +210,6 @@ function parseCustomDate(dateStr) {
return new Date(year, month - 1, day, hour, minute).getTime();
}
-// Determines if a file is editable based on its extension.
export function canEditFile(fileName) {
const allowedExtensions = [
"txt", "html", "htm", "css", "js", "json", "xml",
@@ -60,371 +220,6 @@ export function canEditFile(fileName) {
return allowedExtensions.includes(ext);
}
-// -------------------------------
-// Global Functions (attached to window)
-// -------------------------------
-
-window.toggleRowSelection = function (event, fileName) {
- const targetTag = event.target.tagName.toLowerCase();
- if (targetTag === 'a' || targetTag === 'button' || targetTag === 'input') {
- return;
- }
- const row = event.currentTarget;
- const checkbox = row.querySelector('.file-checkbox');
- if (!checkbox) return;
- checkbox.checked = !checkbox.checked;
- window.updateRowHighlight(checkbox);
- updateFileActionButtons();
-};
-
-window.updateRowHighlight = function (checkbox) {
- const row = checkbox.closest('tr');
- if (!row) return;
- if (checkbox.checked) {
- row.classList.add('row-selected');
- } else {
- row.classList.remove('row-selected');
- }
-};
-
-// -------------------------------
-// File List Rendering
-// -------------------------------
-
-export function loadFileList(folderParam) {
- const folder = folderParam || "root";
- const fileListContainer = document.getElementById("fileList");
-
- // Hide the container while loading
- fileListContainer.style.visibility = "hidden";
- fileListContainer.innerHTML = "Loading files...
";
-
- return fetch("getFileList.php?folder=" + encodeURIComponent(folder) + "&recursive=1&t=" + new Date().getTime())
- .then(response => response.json())
- .then(data => {
- // Clear the container once data is loaded
- fileListContainer.innerHTML = "";
- if (data.files && data.files.length > 0) {
- data.files = data.files.map(file => {
- file.fullName = (file.path || file.name).trim().toLowerCase();
- return file;
- });
- fileData = data.files;
- renderFileTable(folder);
- } else {
- fileListContainer.textContent = "No files found.";
- updateFileActionButtons();
- }
- return data.files || [];
- })
- .catch(error => {
- console.error("Error loading file list:", error);
- fileListContainer.textContent = "Error loading files.";
- return [];
- })
- .finally(() => {
- // Make the container visible after processing
- fileListContainer.style.visibility = "visible";
- });
-}
-
-// Debounce helper (if not defined already)
-function debounce(func, wait) {
- let timeout;
- return function (...args) {
- clearTimeout(timeout);
- timeout = setTimeout(() => func.apply(this, args), wait);
- };
-}
-
-export function renderFileTable(folder) {
- const fileListContainer = document.getElementById("fileList");
- const folderPath = (folder === "root")
- ? "uploads/"
- : "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
-
- // Use the global search term if available.
- const searchTerm = window.currentSearchTerm || "";
-
- const filteredFiles = fileData.filter(file =>
- file.name.toLowerCase().includes(searchTerm.toLowerCase())
- );
-
- // Get persistent items per page from localStorage.
- const itemsPerPageSetting = parseInt(localStorage.getItem('itemsPerPage') || '10', 10);
-
- // Use a mutable currentPage variable
- let currentPage = window.currentPage || 1;
- const totalFiles = filteredFiles.length;
- const totalPages = Math.ceil(totalFiles / itemsPerPageSetting);
-
- // If the current page is greater than totalPages, reset it to a valid page (for example, page 1 or totalPages)
- if (currentPage > totalPages) {
- currentPage = totalPages > 0 ? totalPages : 1;
- window.currentPage = currentPage;
- }
-
- const safeSearchTerm = escapeHTML(searchTerm);
-
- const topControlsHTML = `
-
-
-
-
-
- Page ${currentPage} of ${totalPages || 1}
-
-
-
-
- `;
-
- let tableHTML = `
- `;
-
- const bottomControlsHTML = `
-
-
-
- items per page
-
- `;
-
- fileListContainer.innerHTML = topControlsHTML + tableHTML + tableBody + bottomControlsHTML;
-
- // Re-attach event listener for the new search input element.
- const newSearchInput = document.getElementById("searchInput");
- if (newSearchInput) {
- newSearchInput.addEventListener("input", debounce(function () {
- window.currentSearchTerm = newSearchInput.value;
- window.currentPage = 1;
- renderFileTable(folder);
- // After re-rendering, restore focus and caret position.
- setTimeout(() => {
- const freshInput = document.getElementById("searchInput");
- if (freshInput) {
- freshInput.focus();
- freshInput.setSelectionRange(freshInput.value.length, freshInput.value.length);
- }
- }, 0);
- }, 300));
- }
-
- // Add event listeners for header sorting.
- document.querySelectorAll("table.table thead th[data-column]").forEach(cell => {
- cell.addEventListener("click", function () {
- const column = this.getAttribute("data-column");
- sortFiles(column, folder);
- });
- });
-
- // Add event listeners for checkboxes.
- document.querySelectorAll('#fileList .file-checkbox').forEach(checkbox => {
- checkbox.addEventListener('change', function (e) {
- updateRowHighlight(e.target);
- updateFileActionButtons();
- });
- });
-
- updateFileActionButtons();
-}
-
-// Global function to show an image preview modal.
-window.previewFile = function (fileUrl, fileName) {
- let modal = document.getElementById("filePreviewModal");
- if (!modal) {
- modal = document.createElement("div");
- modal.id = "filePreviewModal";
- // Use the same styling as the original image modal.
- Object.assign(modal.style, {
- display: "none",
- position: "fixed",
- top: "0",
- left: "0",
- width: "100vw",
- height: "100vh",
- backgroundColor: "rgba(0,0,0,0.7)",
- display: "flex",
- justifyContent: "center",
- alignItems: "center",
- zIndex: "1000"
- });
- modal.innerHTML = `
- `;
- document.body.appendChild(modal);
-
- // Close event for the close button.
- document.getElementById("closeFileModal").addEventListener("click", function () {
- const video = modal.querySelector("video");
- if (video) {
- video.pause();
- video.currentTime = 0;
- }
- modal.style.display = "none";
- });
-
- // Close event when clicking outside the modal content.
- modal.addEventListener("click", function (e) {
- if (e.target === modal) {
- const video = modal.querySelector("video");
- if (video) {
- video.pause();
- video.currentTime = 0;
- }
- modal.style.display = "none";
- }
- });
- }
-
- modal.querySelector("h4").textContent = fileName;
- const container = modal.querySelector(".file-preview-container");
- container.innerHTML = ""; // Clear previous content
-
- const extension = fileName.split('.').pop().toLowerCase();
-
- if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic)$/i.test(fileName)) {
- // Image preview
- const img = document.createElement("img");
- img.src = fileUrl;
- img.className = "image-modal-img";
- container.appendChild(img);
- } else if (extension === "pdf") {
- // PDF preview using