diff --git a/CHANGELOG.md b/CHANGELOG.md index d79e86b..7ca1991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## Changes 4/12/2025 + +- **Fuse.js Integration for Indexed Real-Time Searching** + - **Added Fuse.js Library:** Included Fuse.js via a CDN ` + diff --git a/js/fileListView.js b/js/fileListView.js index 5db8f97..69c0963 100644 --- a/js/fileListView.js +++ b/js/fileListView.js @@ -33,11 +33,8 @@ window.viewMode = localStorage.getItem("viewMode") || "table"; // "table" or "ga */ function parseSizeToBytes(sizeStr) { if (!sizeStr) return 0; - // Remove any whitespace let s = sizeStr.trim(); - // Extract the numerical part. let value = parseFloat(s); - // Determine if there is a unit. Convert the unit to uppercase for easier matching. let upper = s.toUpperCase(); if (upper.includes("KB")) { value *= 1024; @@ -50,7 +47,7 @@ function parseSizeToBytes(sizeStr) { } /** - * Format the total bytes as a human-readable string, choosing an appropriate unit. + * Format the total bytes as a human-readable string. */ function formatSize(totalBytes) { if (totalBytes < 1024) { @@ -66,18 +63,33 @@ function formatSize(totalBytes) { /** * Build the folder summary HTML using the filtered file list. - * This function sums the file sizes in bytes correctly, then formats the total. */ function buildFolderSummary(filteredFiles) { const totalFiles = filteredFiles.length; const totalBytes = filteredFiles.reduce((sum, file) => { - // file.size might be something like "456.9KB" or just "1024". return sum + parseSizeToBytes(file.size); }, 0); const sizeStr = formatSize(totalBytes); return `Total Files: ${totalFiles}  |  Total Size: ${sizeStr}`; } +/** + * --- Fuse.js Search Helper --- + * Uses Fuse.js to perform a fuzzy search on fileData. + * Searches over file name, uploader, and tag names. + */ +function searchFiles(searchTerm) { + if (!searchTerm) return fileData; + // Define search options – adjust threshold as needed. + const options = { + keys: ['name', 'uploader', 'tags.name'], + threshold: 0.3 + }; + const fuse = new Fuse(fileData, options); + // Fuse returns an array of results where each result has an "item" property. + return fuse.search(searchTerm).map(result => result.item); +} + /** * --- VIEW MODE TOGGLE BUTTON & Helpers --- */ @@ -134,7 +146,15 @@ export function loadFileList(folderParam) { }) .then(data => { fileListContainer.innerHTML = ""; // Clear loading message. - if (data.files && data.files.length > 0) { + if (data.files && Object.keys(data.files).length > 0) { + // In case the returned "files" is an object instead of an array, transform it. + if (!Array.isArray(data.files)) { + data.files = Object.entries(data.files).map(([name, meta]) => { + meta.name = name; + return meta; + }); + } + // Process each file – add computed properties. data.files = data.files.map(file => { file.fullName = (file.path || file.name).trim().toLowerCase(); file.editable = canEditFile(file.name); @@ -146,7 +166,7 @@ export function loadFileList(folderParam) { }); fileData = data.files; - // Update the file list actions area without removing existing buttons. + // Update file summary. const actionsContainer = document.getElementById("fileListActions"); if (actionsContainer) { let summaryElem = document.getElementById("fileSummary"); @@ -164,7 +184,7 @@ export function loadFileList(folderParam) { summaryElem.innerHTML = buildFolderSummary(fileData); } - // Render the view normally. + // Render view based on the view mode. if (window.viewMode === "gallery") { renderGalleryView(folder); } else { @@ -193,8 +213,7 @@ export function loadFileList(folderParam) { } /** - * Update renderFileTable so that it writes its content into the provided container. - * If no container is provided, it defaults to the element with id "fileList". + * Update renderFileTable so it writes its content into the provided container. */ export function renderFileTable(folder, container) { const fileListContent = container || document.getElementById("fileList"); @@ -202,11 +221,9 @@ export function renderFileTable(folder, container) { const itemsPerPageSetting = parseInt(localStorage.getItem("itemsPerPage") || "10", 10); let currentPage = window.currentPage || 1; - const filteredFiles = fileData.filter(file => { - const nameMatch = file.name.toLowerCase().includes(searchTerm); - const tagMatch = file.tags && file.tags.some(tag => tag.name.toLowerCase().includes(searchTerm)); - return nameMatch || tagMatch; - }); + // 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) { @@ -257,7 +274,7 @@ export function renderFileTable(folder, container) { createViewToggleButton(); - // Setup event listeners as before... + // Setup event listeners. const newSearchInput = document.getElementById("searchInput"); if (newSearchInput) { newSearchInput.addEventListener("input", debounce(function () { @@ -317,10 +334,8 @@ export function renderFileTable(folder, container) { export function renderGalleryView(folder, container) { const fileListContent = container || document.getElementById("fileList"); const searchTerm = (window.currentSearchTerm || "").toLowerCase(); - const filteredFiles = fileData.filter(file => { - return file.name.toLowerCase().includes(searchTerm) || - (file.tags && file.tags.some(tag => tag.name.toLowerCase().includes(searchTerm))); - }); + // Use Fuse.js search for gallery view as well. + const filteredFiles = searchFiles(searchTerm); const folderPath = folder === "root" ? "uploads/" : "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/"; diff --git a/js/i18n.js b/js/i18n.js index 85d4fb3..2c5910c 100644 --- a/js/i18n.js +++ b/js/i18n.js @@ -5,7 +5,7 @@ const translations = { "no_files_selected": "No files selected.", "confirm_delete_files": "Are you sure you want to delete {count} selected file(s)?", "element_not_found": "Element with id \"{id}\" not found.", - "search_placeholder": "Search files or tag...", + "search_placeholder": "Search files, tags, or uploader...", "file_name": "File Name", "date_modified": "Date Modified", "upload_date": "Upload Date",