// fileManager.js
import { escapeHTML, updateFileActionButtons, showToast } from './domUtils.js';
import { formatFolderName } from './folderManager.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
// -------------------------------
// 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(" ");
if (parts.length !== 2) {
return new Date(dateStr).getTime();
}
const datePart = parts[0];
const timePart = parts[1];
const dateComponents = datePart.split("/");
if (dateComponents.length !== 3) {
return new Date(dateStr).getTime();
}
let month = parseInt(dateComponents[0], 10);
let day = parseInt(dateComponents[1], 10);
let year = parseInt(dateComponents[2], 10);
if (year < 100) {
year += 2000;
}
const timeRegex = /^(\d{1,2}):(\d{2})(AM|PM)$/i;
const match = timePart.match(timeRegex);
if (!match) {
return new Date(dateStr).getTime();
}
let hour = parseInt(match[1], 10);
const minute = parseInt(match[2], 10);
const period = match[3].toUpperCase();
if (period === "PM" && hour !== 12) {
hour += 12;
}
if (period === "AM" && hour === 12) {
hour = 0;
}
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",
"md", "py", "ini", "csv", "log", "conf", "config", "bat",
"rtf", "doc", "docx"
];
const ext = fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase();
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";
// Request a recursive listing from the server.
return fetch("getFileList.php?folder=" + encodeURIComponent(folder) + "&recursive=1&t=" + new Date().getTime())
.then(response => response.json())
.then(data => {
const fileListContainer = document.getElementById("fileList");
fileListContainer.innerHTML = "";
if (data.files && data.files.length > 0) {
// Map each file so that we have a full name that includes subfolder information.
// We assume that getFileList.php returns a property 'path' that contains the full relative path (e.g. "subfolder/filename.txt")
data.files = data.files.map(file => {
// If file.path exists, use that; otherwise fallback to file.name.
file.fullName = (file.path || file.name).trim().toLowerCase();
return file;
});
// Save fileData and render file table using your full list.
fileData = data.files;
renderFileTable(folder);
} else {
fileListContainer.textContent = "No files found.";
updateFileActionButtons();
}
// Return the full file objects.
return data.files || [];
})
.catch(error => {
console.error("Error loading file list:", error);
return [];
});
}
// 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);
document.getElementById("closeFileModal").addEventListener("click", function () {
modal.style.display = "none";
});
modal.addEventListener("click", function (e) {
if (e.target === modal) {
modal.style.display = "none";
}
});
}
modal.querySelector("h4").textContent = "Preview: " + 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