// fileListView.js import { escapeHTML, debounce, buildSearchAndPaginationControls, buildFileTableHeader, buildFileTableRow, buildBottomControls, updateFileActionButtons, showToast, updateRowHighlight, toggleRowSelection, attachEnterKeyListener } from './domUtils.js'; import { t } from './i18n.js'; import { bindFileListContextMenu } from './fileMenu.js'; import { openDownloadModal } from './fileActions.js'; import { openTagModal, openMultiTagModal } from './fileTags.js'; import { getParentFolder, updateBreadcrumbTitle, setupBreadcrumbDelegation } from './folderManager.js'; import { folderDragOverHandler, folderDragLeaveHandler, folderDropHandler } from './fileDragDrop.js'; export let fileData = []; export let sortOrder = { column: "uploaded", ascending: true }; window.itemsPerPage = parseInt( localStorage.getItem('itemsPerPage') || window.itemsPerPage || '10', 10 ); window.currentPage = window.currentPage || 1; window.viewMode = localStorage.getItem("viewMode") || "table"; // Global flag for advanced search mode. window.advancedSearchEnabled = false; /** * --- Helper Functions --- */ /** * Convert a file size string (e.g. "456.9KB", "1.2 MB", "1024") into bytes. */ function parseSizeToBytes(sizeStr) { if (!sizeStr) return 0; let s = sizeStr.trim(); let value = parseFloat(s); let upper = s.toUpperCase(); if (upper.includes("KB")) { value *= 1024; } else if (upper.includes("MB")) { value *= 1024 * 1024; } else if (upper.includes("GB")) { value *= 1024 * 1024 * 1024; } return value; } /** * Format the total bytes as a human-readable string. */ function formatSize(totalBytes) { if (totalBytes < 1024) { return totalBytes + " Bytes"; } else if (totalBytes < 1024 * 1024) { return (totalBytes / 1024).toFixed(2) + " KB"; } else if (totalBytes < 1024 * 1024 * 1024) { return (totalBytes / (1024 * 1024)).toFixed(2) + " MB"; } else { return (totalBytes / (1024 * 1024 * 1024)).toFixed(2) + " GB"; } } /** * Build the folder summary HTML using the filtered file list. */ function buildFolderSummary(filteredFiles) { const totalFiles = filteredFiles.length; const totalBytes = filteredFiles.reduce((sum, file) => { return sum + parseSizeToBytes(file.size); }, 0); const sizeStr = formatSize(totalBytes); return `${t('total_files')}: ${totalFiles}  |  ${t('total_size')}: ${sizeStr}`; } /** * --- Advanced Search Toggle --- * Toggles advanced search mode. When enabled, the search will include additional keys (e.g. "content"). */ function toggleAdvancedSearch() { window.advancedSearchEnabled = !window.advancedSearchEnabled; const advancedBtn = document.getElementById("advancedSearchToggle"); if (advancedBtn) { advancedBtn.textContent = window.advancedSearchEnabled ? "Basic Search" : "Advanced Search"; } // Re-run the file table rendering with updated search settings. renderFileTable(window.currentFolder); } window.imageCache = window.imageCache || {}; function cacheImage(imgElem, key) { // Save the current src for future renders. window.imageCache[key] = imgElem.src; } window.cacheImage = cacheImage; /** * --- Fuse.js Search Helper --- * Uses Fuse.js to perform a fuzzy search on fileData. * By default, searches over file name, uploader, and tag names. * When advanced search is enabled, it also includes the 'content' property. */ function searchFiles(searchTerm) { if (!searchTerm) return fileData; // Define search keys. let keys = [ { name: 'name', weight: 0.1 }, { name: 'uploader', weight: 0.1 }, { name: 'tags.name', weight: 0.1 } ]; if (window.advancedSearchEnabled) { keys.push({ name: 'content', weight: 0.7 }); } const options = { keys: keys, threshold: 0.4, minMatchCharLength: 2, ignoreLocation: true }; const fuse = new Fuse(fileData, options); let results = fuse.search(searchTerm); return results.map(result => result.item); } /** * --- VIEW MODE TOGGLE BUTTON & Helpers --- */ export function createViewToggleButton() { let toggleBtn = document.getElementById("toggleViewBtn"); if (!toggleBtn) { toggleBtn = document.createElement("button"); toggleBtn.id = "toggleViewBtn"; toggleBtn.classList.add("btn", "btn-toggleview"); // Set initial icon and tooltip based on current view mode. if (window.viewMode === "gallery") { toggleBtn.innerHTML = 'view_list'; toggleBtn.title = t("switch_to_table_view"); } else { toggleBtn.innerHTML = 'view_module'; toggleBtn.title = t("switch_to_gallery_view"); } // Insert the button before the last button in the header. const headerButtons = document.querySelector(".header-buttons"); if (headerButtons && headerButtons.lastElementChild) { headerButtons.insertBefore(toggleBtn, headerButtons.lastElementChild); } else if (headerButtons) { headerButtons.appendChild(toggleBtn); } } toggleBtn.onclick = () => { window.viewMode = window.viewMode === "gallery" ? "table" : "gallery"; localStorage.setItem("viewMode", window.viewMode); loadFileList(window.currentFolder); if (window.viewMode === "gallery") { toggleBtn.innerHTML = 'view_list'; toggleBtn.title = t("switch_to_table_view"); } else { toggleBtn.innerHTML = 'view_module'; toggleBtn.title = t("switch_to_gallery_view"); } }; return toggleBtn; } export function formatFolderName(folder) { if (folder === "root") return "(Root)"; return folder .replace(/[_-]+/g, " ") .replace(/\b\w/g, char => char.toUpperCase()); } // Expose inline DOM helpers. window.toggleRowSelection = toggleRowSelection; window.updateRowHighlight = updateRowHighlight; export async function loadFileList(folderParam) { const folder = folderParam || "root"; const fileListContainer = document.getElementById("fileList"); const actionsContainer = document.getElementById("fileListActions"); // 1) show loader fileListContainer.style.visibility = "hidden"; fileListContainer.innerHTML = "
Loading files...
"; try { // 2) fetch files + folders in parallel const [filesRes, foldersRes] = await Promise.all([ fetch(`/api/file/getFileList.php?folder=${encodeURIComponent(folder)}&recursive=1&t=${Date.now()}`), fetch(`/api/folder/getFolderList.php?folder=${encodeURIComponent(folder)}`) ]); if (filesRes.status === 401) { window.location.href = "/api/auth/logout.php"; throw new Error("Unauthorized"); } const data = await filesRes.json(); const folderRaw = await foldersRes.json(); // --- build ONLY the *direct* children of current folder --- let subfolders = []; const hidden = new Set(["profile_pics", "trash"]); if (Array.isArray(folderRaw)) { const allPaths = folderRaw.map(item => item.folder ?? item); const depth = folder === "root" ? 1 : folder.split("/").length + 1; subfolders = allPaths .filter(p => { if (folder === "root") { return p.indexOf("/") === -1; } if (!p.startsWith(folder + "/")) return false; return p.split("/").length === depth; }) .map(p => ({ name: p.split("/").pop(), full: p })); } subfolders = subfolders.filter(sf => !hidden.has(sf.name)); // 3) clear loader fileListContainer.innerHTML = ""; // 4) handle “no files” case if (!data.files || Object.keys(data.files).length === 0) { fileListContainer.textContent = t("no_files_found"); // hide summary const summaryElem = document.getElementById("fileSummary"); if (summaryElem) summaryElem.style.display = "none"; // hide slider const sliderContainer = document.getElementById("viewSliderContainer"); if (sliderContainer) sliderContainer.style.display = "none"; // hide folder strip const strip = document.getElementById("folderStripContainer"); if (strip) strip.style.display = "none"; updateFileActionButtons(); return []; } // 5) normalize files array if (!Array.isArray(data.files)) { data.files = Object.entries(data.files).map(([name, meta]) => { meta.name = name; return meta; }); } data.files = data.files.map(f => { f.fullName = (f.path || f.name).trim().toLowerCase(); f.editable = canEditFile(f.name); f.folder = folder; return f; }); fileData = data.files; // 6) inject summary + slider if (actionsContainer) { // a) summary let summaryElem = document.getElementById("fileSummary"); if (!summaryElem) { summaryElem = document.createElement("div"); summaryElem.id = "fileSummary"; summaryElem.style.cssText = "float:right; margin:0 60px 0 auto; font-size:0.9em;"; actionsContainer.appendChild(summaryElem); } summaryElem.style.display = "block"; summaryElem.innerHTML = buildFolderSummary(fileData); // b) slider const viewMode = window.viewMode || "table"; let sliderContainer = document.getElementById("viewSliderContainer"); if (!sliderContainer) { sliderContainer = document.createElement("div"); sliderContainer.id = "viewSliderContainer"; sliderContainer.style.cssText = "display:inline-flex; align-items:center; margin-right:auto; font-size:0.9em;"; actionsContainer.insertBefore(sliderContainer, summaryElem); } else { sliderContainer.style.display = "inline-flex"; } if (viewMode === "gallery") { const w = window.innerWidth; let maxCols; if (w < 600) maxCols = 1; else if (w < 900) maxCols = 2; else if (w < 1200) maxCols = 4; else maxCols = 6; const currentCols = Math.min( parseInt(localStorage.getItem("galleryColumns") || "3", 10), maxCols ); sliderContainer.innerHTML = ` ${currentCols} `; const gallerySlider = document.getElementById("galleryColumnsSlider"); const galleryValue = document.getElementById("galleryColumnsValue"); gallerySlider.oninput = e => { const v = +e.target.value; localStorage.setItem("galleryColumns", v); galleryValue.textContent = v; document.querySelector(".gallery-container") ?.style.setProperty("grid-template-columns", `repeat(${v},1fr)`); }; } else { const currentHeight = parseInt(localStorage.getItem("rowHeight") || "48", 10); sliderContainer.innerHTML = ` ${currentHeight}px `; const rowSlider = document.getElementById("rowHeightSlider"); const rowValue = document.getElementById("rowHeightValue"); rowSlider.oninput = e => { const v = e.target.value; document.documentElement.style.setProperty("--file-row-height", v + "px"); localStorage.setItem("rowHeight", v); rowValue.textContent = v + "px"; }; } } // 7) inject folder strip below actions, above file list let strip = document.getElementById("folderStripContainer"); if (!strip) { strip = document.createElement("div"); strip.id = "folderStripContainer"; strip.className = "folder-strip-container"; actionsContainer.parentNode.insertBefore(strip, actionsContainer); } if (window.showFoldersInList && subfolders.length) { strip.innerHTML = subfolders.map(sf => `
folder
${escapeHTML(sf.name)}
`).join(""); strip.style.display = "flex"; strip.querySelectorAll(".folder-item").forEach(el => { // click‐to‐navigate el.addEventListener("click", () => { const dest = el.dataset.folder; window.currentFolder = dest; localStorage.setItem("lastOpenedFolder", dest); updateBreadcrumbTitle(dest); document.querySelectorAll(".folder-option.selected") .forEach(o => o.classList.remove("selected")); document.querySelector(`.folder-option[data-folder="${dest}"]`) ?.classList.add("selected"); loadFileList(dest); }); // drag & drop handlers el.addEventListener("dragover", folderDragOverHandler); el.addEventListener("dragleave", folderDragLeaveHandler); el.addEventListener("drop", folderDropHandler); }); } else { strip.style.display = "none"; } // 8) render files if (window.viewMode === "gallery") { renderGalleryView(folder); } else { renderFileTable(folder); } updateFileActionButtons(); return data.files; } catch (err) { console.error("Error loading file list:", err); if (err.message !== "Unauthorized") { fileListContainer.textContent = "Error loading files."; } return []; } finally { fileListContainer.style.visibility = "visible"; } } /** * Update renderFileTable so it writes its content into the provided container. */ export function renderFileTable(folder, container, subfolders) { const fileListContent = container || document.getElementById("fileList"); const searchTerm = (window.currentSearchTerm || "").toLowerCase(); const itemsPerPageSetting = parseInt(localStorage.getItem("itemsPerPage") || "10", 10); let currentPage = window.currentPage || 1; // Use Fuse.js search via our helper function. const filteredFiles = searchFiles(searchTerm); 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("/") + "/"; // Build the top controls and append the advanced search toggle button. const topControlsHTML = buildSearchAndPaginationControls({ currentPage, totalPages, searchTerm: window.currentSearchTerm || "" }); const combinedTopHTML = topControlsHTML; let 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, idx) => { let rowHTML = buildFileTableRow(file, folderPath); rowHTML = rowHTML.replace(" 0) { tagBadgesHTML = '
'; file.tags.forEach(tag => { tagBadgesHTML += `${escapeHTML(tag.name)}`; }); tagBadgesHTML += "
"; } rowHTML = rowHTML.replace(/()(.*?)(<\/td>)/, (match, p1, p2, p3) => { return p1 + p2 + tagBadgesHTML + p3; }); rowsHTML += rowHTML; }); } else { rowsHTML += `No files found.`; } rowsHTML += ""; const bottomControlsHTML = buildBottomControls(itemsPerPageSetting); fileListContent.innerHTML = combinedTopHTML + headerHTML + rowsHTML + bottomControlsHTML; fileListContent.querySelectorAll('.folder-item').forEach(el => { el.addEventListener('click', () => loadFileList(el.dataset.folder)); }); // pagination clicks const prevBtn = document.getElementById("prevPageBtn"); if (prevBtn) prevBtn.addEventListener("click", () => { if (window.currentPage > 1) { window.currentPage--; renderFileTable(folder, container); } }); const nextBtn = document.getElementById("nextPageBtn"); if (nextBtn) nextBtn.addEventListener("click", () => { // totalPages is computed above in this scope if (window.currentPage < totalPages) { window.currentPage++; renderFileTable(folder, container); } }); // ADD: advanced search toggle const advToggle = document.getElementById("advancedSearchToggle"); if (advToggle) advToggle.addEventListener("click", () => { toggleAdvancedSearch(); }); // items-per-page selector const itemsSelect = document.getElementById("itemsPerPageSelect"); if (itemsSelect) itemsSelect.addEventListener("change", e => { window.itemsPerPage = parseInt(e.target.value, 10); localStorage.setItem("itemsPerPage", window.itemsPerPage); window.currentPage = 1; renderFileTable(folder, container); }); // hook up the master checkbox const selectAll = document.getElementById("selectAll"); if (selectAll) { selectAll.addEventListener("change", () => { toggleAllCheckboxes(selectAll); }); } // 1) Row-click selects the row fileListContent.querySelectorAll("tbody tr").forEach(row => { row.addEventListener("click", e => { // grab the underlying checkbox value const cb = row.querySelector(".file-checkbox"); if (!cb) return; toggleRowSelection(e, cb.value); }); }); // 2) Download buttons fileListContent.querySelectorAll(".download-btn").forEach(btn => { btn.addEventListener("click", e => { e.stopPropagation(); openDownloadModal(btn.dataset.downloadName, btn.dataset.downloadFolder); }); }); // 3) Edit buttons fileListContent.querySelectorAll(".edit-btn").forEach(btn => { btn.addEventListener("click", e => { e.stopPropagation(); editFile(btn.dataset.editName, btn.dataset.editFolder); }); }); // 4) Rename buttons fileListContent.querySelectorAll(".rename-btn").forEach(btn => { btn.addEventListener("click", e => { e.stopPropagation(); renameFile(btn.dataset.renameName, btn.dataset.renameFolder); }); }); // 5) Preview buttons fileListContent.querySelectorAll(".preview-btn").forEach(btn => { btn.addEventListener("click", e => { e.stopPropagation(); previewFile(btn.dataset.previewUrl, btn.dataset.previewName); }); }); createViewToggleButton(); // Setup event listeners. const newSearchInput = document.getElementById("searchInput"); if (newSearchInput) { newSearchInput.addEventListener("input", debounce(function () { window.currentSearchTerm = newSearchInput.value; window.currentPage = 1; renderFileTable(folder, container); setTimeout(() => { const freshInput = document.getElementById("searchInput"); if (freshInput) { freshInput.focus(); const len = freshInput.value.length; freshInput.setSelectionRange(len, len); } }, 0); }, 300)); } const slider = document.getElementById('rowHeightSlider'); const valueDisplay = document.getElementById('rowHeightValue'); if (slider) { slider.addEventListener('input', e => { const v = +e.target.value; // slider value in px document.documentElement.style.setProperty('--file-row-height', v + 'px'); localStorage.setItem('rowHeight', v); valueDisplay.textContent = v + 'px'; }); } 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(); }); }); document.querySelectorAll(".share-btn").forEach(btn => { btn.addEventListener("click", function (e) { e.stopPropagation(); const fileName = this.getAttribute("data-file"); const file = fileData.find(f => f.name === fileName); if (file) { import('./filePreview.js').then(module => { module.openShareModal(file, folder); }); } }); }); updateFileActionButtons(); document.querySelectorAll("#fileList tbody tr").forEach(row => { row.setAttribute("draggable", "true"); import('./fileDragDrop.js').then(module => { row.addEventListener("dragstart", module.fileDragStartHandler); }); }); document.querySelectorAll(".download-btn, .edit-btn, .rename-btn").forEach(btn => { btn.addEventListener("click", e => e.stopPropagation()); }); bindFileListContextMenu(); } // A helper to compute the max image height based on the current column count. function getMaxImageHeight() { const columns = parseInt(window.galleryColumns || 3, 10); return 150 * (7 - columns); // adjust the multiplier as needed. } export function renderGalleryView(folder, container) { const fileListContent = container || document.getElementById("fileList"); const searchTerm = (window.currentSearchTerm || "").toLowerCase(); const filteredFiles = searchFiles(searchTerm); const folderPath = folder === "root" ? "uploads/" : "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/"; // pagination settings const itemsPerPage = window.itemsPerPage; let currentPage = window.currentPage || 1; const totalFiles = filteredFiles.length; const totalPages = Math.ceil(totalFiles / itemsPerPage); if (currentPage > totalPages) { currentPage = totalPages || 1; window.currentPage = currentPage; } // --- Top controls: search + pagination + items-per-page --- let galleryHTML = buildSearchAndPaginationControls({ currentPage, totalPages, searchTerm: window.currentSearchTerm || "" }); // wire up search input just like table view setTimeout(() => { const searchInput = document.getElementById("searchInput"); if (searchInput) { searchInput.addEventListener("input", debounce(() => { window.currentSearchTerm = searchInput.value; window.currentPage = 1; renderGalleryView(folder); // keep caret at end setTimeout(() => { const f = document.getElementById("searchInput"); if (f) { f.focus(); const len = f.value.length; f.setSelectionRange(len, len); } }, 0); }, 300)); } }, 0); // --- Column slider with responsive max --- const numColumns = window.galleryColumns || 3; // clamp slider max to 1 on small (<600px), 2 on medium (<900px), else up to 6 const w = window.innerWidth; let maxCols = 6; if (w < 600) maxCols = 1; else if (w < 900) maxCols = 2; // ensure current value doesn’t exceed the new max const startCols = Math.min(numColumns, maxCols); window.galleryColumns = startCols; // --- Start gallery grid --- galleryHTML += ` `; // end gallery-container // bottom controls galleryHTML += buildBottomControls(itemsPerPage); // render fileListContent.innerHTML = galleryHTML; // --- Now wire up all behaviors without inline handlers --- // ADD: pagination buttons for gallery const prevBtn = document.getElementById("prevPageBtn"); if (prevBtn) prevBtn.addEventListener("click", () => { if (window.currentPage > 1) { window.currentPage--; renderGalleryView(folder, container); } }); const nextBtn = document.getElementById("nextPageBtn"); if (nextBtn) nextBtn.addEventListener("click", () => { if (window.currentPage < totalPages) { window.currentPage++; renderGalleryView(folder, container); } }); // ←— ADD: advanced search toggle const advToggle = document.getElementById("advancedSearchToggle"); if (advToggle) advToggle.addEventListener("click", () => { toggleAdvancedSearch(); }); // ←— ADD: wire up context-menu in gallery bindFileListContextMenu(); // ADD: items-per-page selector for gallery const itemsSelect = document.getElementById("itemsPerPageSelect"); if (itemsSelect) itemsSelect.addEventListener("change", e => { window.itemsPerPage = parseInt(e.target.value, 10); localStorage.setItem("itemsPerPage", window.itemsPerPage); window.currentPage = 1; renderGalleryView(folder, container); }); // cache images on load fileListContent.querySelectorAll('.gallery-thumbnail').forEach(img => { const key = img.dataset.cacheKey; img.addEventListener('load', () => cacheImage(img, key)); }); // preview clicks fileListContent.querySelectorAll(".gallery-preview").forEach(el => { el.addEventListener("click", () => { previewFile(el.dataset.previewUrl, el.dataset.previewName); }); }); // download clicks fileListContent.querySelectorAll(".download-btn").forEach(btn => { btn.addEventListener("click", e => { e.stopPropagation(); openDownloadModal(btn.dataset.downloadName, btn.dataset.downloadFolder); }); }); // edit clicks fileListContent.querySelectorAll(".edit-btn").forEach(btn => { btn.addEventListener("click", e => { e.stopPropagation(); editFile(btn.dataset.editName, btn.dataset.editFolder); }); }); // rename clicks fileListContent.querySelectorAll(".rename-btn").forEach(btn => { btn.addEventListener("click", e => { e.stopPropagation(); renameFile(btn.dataset.renameName, btn.dataset.renameFolder); }); }); // share clicks fileListContent.querySelectorAll(".share-btn").forEach(btn => { btn.addEventListener("click", e => { e.stopPropagation(); const fileName = btn.dataset.file; const fileObj = fileData.find(f => f.name === fileName); if (fileObj) { import('./filePreview.js').then(m => m.openShareModal(fileObj, folder)); } }); }); // checkboxes fileListContent.querySelectorAll(".file-checkbox").forEach(cb => { cb.addEventListener("change", () => updateFileActionButtons()); }); // slider const slider = document.getElementById("galleryColumnsSlider"); if (slider) { slider.addEventListener("input", () => { const v = +slider.value; document.getElementById("galleryColumnsValue").textContent = v; window.galleryColumns = v; document.querySelector(".gallery-container") .style.gridTemplateColumns = `repeat(${v},1fr)`; document.querySelectorAll(".gallery-thumbnail") .forEach(img => img.style.maxHeight = getMaxImageHeight() + "px"); }); } // pagination functions window.changePage = newPage => { window.currentPage = newPage; if (window.viewMode === "gallery") renderGalleryView(folder); else renderFileTable(folder); }; window.changeItemsPerPage = cnt => { window.itemsPerPage = +cnt; localStorage.setItem("itemsPerPage", cnt); window.currentPage = 1; if (window.viewMode === "gallery") renderGalleryView(folder); else renderFileTable(folder); }; // update toolbar and toggle button updateFileActionButtons(); createViewToggleButton(); } // Responsive slider constraints based on screen size. function updateSliderConstraints() { const slider = document.getElementById("galleryColumnsSlider"); if (!slider) return; const width = window.innerWidth; let min = 1; let max; // Set maximum based on screen size. if (width < 600) { // small devices (phones) max = 1; } else if (width < 1024) { // medium devices max = 3; } else if (width < 1440) { // between medium and large devices max = 4; } else { // large devices and above max = 6; } // Adjust the slider's current value if needed let currentVal = parseInt(slider.value, 10); if (currentVal > max) { currentVal = max; slider.value = max; } slider.min = min; slider.max = max; document.getElementById("galleryColumnsValue").textContent = currentVal; // Update the grid layout based on the current slider value. const galleryContainer = document.querySelector(".gallery-container"); if (galleryContainer) { galleryContainer.style.gridTemplateColumns = `repeat(${currentVal}, 1fr)`; } } window.addEventListener('load', updateSliderConstraints); window.addEventListener('resize', updateSliderConstraints); 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; }); if (window.viewMode === "gallery") { renderGalleryView(folder); } else { renderFileTable(folder); } } 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(); } 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); } // Expose global functions for pagination and preview. window.changePage = function (newPage) { window.currentPage = newPage; if (window.viewMode === 'gallery') { renderGalleryView(window.currentFolder); } else { renderFileTable(window.currentFolder); } }; window.changeItemsPerPage = function (newCount) { window.itemsPerPage = parseInt(newCount, 10); localStorage.setItem('itemsPerPage', newCount); window.currentPage = 1; if (window.viewMode === 'gallery') { renderGalleryView(window.currentFolder); } else { renderFileTable(window.currentFolder); } }; // fileListView.js (bottom) window.loadFileList = loadFileList; window.renderFileTable = renderFileTable; window.renderGalleryView = renderGalleryView; window.sortFiles = sortFiles; window.toggleAdvancedSearch = toggleAdvancedSearch;