Advanced Search Implementation
This commit is contained in:
27
CHANGELOG.md
27
CHANGELOG.md
@@ -2,12 +2,27 @@
|
|||||||
|
|
||||||
## Changes 4/12/2025
|
## Changes 4/12/2025
|
||||||
|
|
||||||
- **Fuse.js Integration for Indexed Real-Time Searching**
|
### Advanced Search Implementation
|
||||||
- **Added Fuse.js Library:** Included Fuse.js via a CDN `<script>` tag to leverage its client‑side fuzzy search capabilities.
|
|
||||||
- **Created searchFiles Helper Function:** Introduced a new function that uses Fuse.js to build an index and perform fuzzy searches over file properties (file name, uploader, and nested tag names).
|
- **Advanced Search Toggle:**
|
||||||
- **Transformed JSON Object to Array:** Updated the loadFileList() function to convert the returned file data into an array (if it isn’t already) and assign file names from JSON keys.
|
- Added a global toggle (`window.advancedSearchEnabled`) and a UI button to switch between basic and advanced search modes.
|
||||||
- **Updated Rendering Functions:** Modified both renderFileTable() and renderGalleryView() to use the searchFiles() helper instead of a simple in‑array .filter(). This ensures that every search—real‑time by user input—is powered by Fuse.js’s indexed search.
|
- The toggle button label changes between "Advanced Search" and "Basic Search" to reflect the active mode.
|
||||||
- **Enhanced Search Configuration:** Configured Fuse.js to search across multiple keys (file name, uploader, and tags) so that users can find files based on any of these properties.
|
|
||||||
|
- **Fuse.js Integration Updates:**
|
||||||
|
- Modified the `searchFiles()` function to conditionally include the `"content"` key in the Fuse.js keys only when advanced search mode is enabled.
|
||||||
|
- Adjusted Fuse.js options by adding `ignoreLocation: true`, adjusting the `threshold`, and optionally assigning weights (e.g., a lower weight for `name` and a higher weight for `content`) to prioritize matches in file content.
|
||||||
|
|
||||||
|
- **Backend (PHP) Enhancements:**
|
||||||
|
- Updated **getFileList.php** to read the content of text-based files (e.g., `.txt`, `.html`, `.md`, etc.) using `file_get_contents()`.
|
||||||
|
- Added a `"content"` property to the JSON response for eligible files to allow for full-text search in advanced mode.
|
||||||
|
|
||||||
|
### Fuse.js Integration for Indexed Real-Time Searching**
|
||||||
|
|
||||||
|
- **Added Fuse.js Library:** Included Fuse.js via a CDN `<script>` tag to leverage its client‑side fuzzy search capabilities.
|
||||||
|
- **Created searchFiles Helper Function:** Introduced a new function that uses Fuse.js to build an index and perform fuzzy searches over file properties (file name, uploader, and nested tag names).
|
||||||
|
- **Transformed JSON Object to Array:** Updated the loadFileList() function to convert the returned file data into an array (if it isn’t already) and assign file names from JSON keys.
|
||||||
|
- **Updated Rendering Functions:** Modified both renderFileTable() and renderGalleryView() to use the searchFiles() helper instead of a simple in‑array .filter(). This ensures that every search—real‑time by user input—is powered by Fuse.js’s indexed search.
|
||||||
|
- **Enhanced Search Configuration:** Configured Fuse.js to search across multiple keys (file name, uploader, and tags) so that users can find files based on any of these properties.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -25,11 +25,6 @@ if ($folder !== 'root') {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper: Generate the metadata file path for a given folder.
|
* Helper: Generate the metadata file path for a given folder.
|
||||||
* For "root", returns "root_metadata.json". Otherwise, replaces slashes,
|
|
||||||
* backslashes, and spaces with dashes and appends "_metadata.json".
|
|
||||||
*
|
|
||||||
* @param string $folder The folder's relative path.
|
|
||||||
* @return string The full path to the folder's metadata file.
|
|
||||||
*/
|
*/
|
||||||
function getMetadataFilePath($folder) {
|
function getMetadataFilePath($folder) {
|
||||||
if (strtolower($folder) === 'root' || $folder === '') {
|
if (strtolower($folder) === 'root' || $folder === '') {
|
||||||
@@ -69,7 +64,6 @@ foreach ($files as $file) {
|
|||||||
|
|
||||||
// Since metadata is stored per folder, the key is simply the file name.
|
// Since metadata is stored per folder, the key is simply the file name.
|
||||||
$metaKey = $file;
|
$metaKey = $file;
|
||||||
|
|
||||||
$fileDateModified = filemtime($filePath) ? date(DATE_TIME_FORMAT, filemtime($filePath)) : "Unknown";
|
$fileDateModified = filemtime($filePath) ? date(DATE_TIME_FORMAT, filemtime($filePath)) : "Unknown";
|
||||||
$fileUploadedDate = isset($metadata[$metaKey]["uploaded"]) ? $metadata[$metaKey]["uploaded"] : "Unknown";
|
$fileUploadedDate = isset($metadata[$metaKey]["uploaded"]) ? $metadata[$metaKey]["uploaded"] : "Unknown";
|
||||||
$fileUploader = isset($metadata[$metaKey]["uploader"]) ? $metadata[$metaKey]["uploader"] : "Unknown";
|
$fileUploader = isset($metadata[$metaKey]["uploader"]) ? $metadata[$metaKey]["uploader"] : "Unknown";
|
||||||
@@ -85,7 +79,8 @@ foreach ($files as $file) {
|
|||||||
$fileSizeFormatted = sprintf("%s bytes", number_format($fileSizeBytes));
|
$fileSizeFormatted = sprintf("%s bytes", number_format($fileSizeBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
$fileList[] = [
|
// Build the basic file entry.
|
||||||
|
$fileEntry = [
|
||||||
'name' => $file,
|
'name' => $file,
|
||||||
'modified' => $fileDateModified,
|
'modified' => $fileDateModified,
|
||||||
'uploaded' => $fileUploadedDate,
|
'uploaded' => $fileUploadedDate,
|
||||||
@@ -93,6 +88,14 @@ foreach ($files as $file) {
|
|||||||
'uploader' => $fileUploader,
|
'uploader' => $fileUploader,
|
||||||
'tags' => isset($metadata[$metaKey]['tags']) ? $metadata[$metaKey]['tags'] : []
|
'tags' => isset($metadata[$metaKey]['tags']) ? $metadata[$metaKey]['tags'] : []
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add file content for text-based files.
|
||||||
|
if (preg_match('/\.(txt|html|htm|md|js|css|json|xml|php|py|ini|conf|log)$/i', $file)) {
|
||||||
|
$content = file_get_contents($filePath);
|
||||||
|
$fileEntry['content'] = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileList[] = $fileEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load global tags from createdTags.json.
|
// Load global tags from createdTags.json.
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ window.itemsPerPage = window.itemsPerPage || 10;
|
|||||||
window.currentPage = window.currentPage || 1;
|
window.currentPage = window.currentPage || 1;
|
||||||
window.viewMode = localStorage.getItem("viewMode") || "table"; // "table" or "gallery"
|
window.viewMode = localStorage.getItem("viewMode") || "table"; // "table" or "gallery"
|
||||||
|
|
||||||
|
// Global flag for advanced search mode.
|
||||||
|
window.advancedSearchEnabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --- Helper Functions ---
|
* --- Helper Functions ---
|
||||||
*/
|
*/
|
||||||
@@ -73,22 +76,50 @@ function buildFolderSummary(filteredFiles) {
|
|||||||
return `<strong>Total Files:</strong> ${totalFiles} | <strong>Total Size:</strong> ${sizeStr}`;
|
return `<strong>Total Files:</strong> ${totalFiles} | <strong>Total Size:</strong> ${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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --- Fuse.js Search Helper ---
|
* --- Fuse.js Search Helper ---
|
||||||
* Uses Fuse.js to perform a fuzzy search on fileData.
|
* Uses Fuse.js to perform a fuzzy search on fileData.
|
||||||
* Searches over file name, uploader, and tag names.
|
* By default, searches over file name, uploader, and tag names.
|
||||||
|
* When advanced search is enabled, it also includes the 'content' property.
|
||||||
*/
|
*/
|
||||||
function searchFiles(searchTerm) {
|
function searchFiles(searchTerm) {
|
||||||
if (!searchTerm) return fileData;
|
if (!searchTerm) return fileData;
|
||||||
// Define search options – adjust threshold as needed.
|
|
||||||
|
// 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 = {
|
const options = {
|
||||||
keys: ['name', 'uploader', 'tags.name'],
|
keys: keys,
|
||||||
threshold: 0.3
|
threshold: 0.4,
|
||||||
|
minMatchCharLength: 2,
|
||||||
|
ignoreLocation: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const fuse = new Fuse(fileData, options);
|
const fuse = new Fuse(fileData, options);
|
||||||
// Fuse returns an array of results where each result has an "item" property.
|
let results = fuse.search(searchTerm);
|
||||||
return fuse.search(searchTerm).map(result => result.item);
|
return results.map(result => result.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --- VIEW MODE TOGGLE BUTTON & Helpers ---
|
* --- VIEW MODE TOGGLE BUTTON & Helpers ---
|
||||||
@@ -147,7 +178,7 @@ export function loadFileList(folderParam) {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
fileListContainer.innerHTML = ""; // Clear loading message.
|
fileListContainer.innerHTML = ""; // Clear loading message.
|
||||||
if (data.files && Object.keys(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 the returned "files" is an object instead of an array, transform it.
|
||||||
if (!Array.isArray(data.files)) {
|
if (!Array.isArray(data.files)) {
|
||||||
data.files = Object.entries(data.files).map(([name, meta]) => {
|
data.files = Object.entries(data.files).map(([name, meta]) => {
|
||||||
meta.name = name;
|
meta.name = name;
|
||||||
@@ -162,6 +193,8 @@ export function loadFileList(folderParam) {
|
|||||||
if (!file.type && /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(file.name)) {
|
if (!file.type && /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(file.name)) {
|
||||||
file.type = "image";
|
file.type = "image";
|
||||||
}
|
}
|
||||||
|
// OPTIONAL: For text documents, preload content (if available from backend)
|
||||||
|
// Example: if (/\.txt|html|md|js|css|json|xml$/i.test(file.name)) { file.content = file.content || ""; }
|
||||||
return file;
|
return file;
|
||||||
});
|
});
|
||||||
fileData = data.files;
|
fileData = data.files;
|
||||||
@@ -234,11 +267,17 @@ export function renderFileTable(folder, container) {
|
|||||||
? "uploads/"
|
? "uploads/"
|
||||||
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
|
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
|
||||||
|
|
||||||
|
// Build the top controls and append the advanced search toggle button.
|
||||||
const topControlsHTML = buildSearchAndPaginationControls({
|
const topControlsHTML = buildSearchAndPaginationControls({
|
||||||
currentPage,
|
currentPage,
|
||||||
totalPages,
|
totalPages,
|
||||||
searchTerm: window.currentSearchTerm || ""
|
searchTerm: window.currentSearchTerm || ""
|
||||||
});
|
});
|
||||||
|
const advancedToggleHTML = `<button id="advancedSearchToggle" class="btn btn-sm btn-outline-secondary" style="margin-left: 10px;" onclick="toggleAdvancedSearch()">
|
||||||
|
${window.advancedSearchEnabled ? "Basic Search" : "Advanced Search"}
|
||||||
|
</button>`;
|
||||||
|
const combinedTopHTML = topControlsHTML + advancedToggleHTML;
|
||||||
|
|
||||||
let headerHTML = buildFileTableHeader(sortOrder);
|
let headerHTML = buildFileTableHeader(sortOrder);
|
||||||
const startIndex = (currentPage - 1) * itemsPerPageSetting;
|
const startIndex = (currentPage - 1) * itemsPerPageSetting;
|
||||||
const endIndex = Math.min(startIndex + itemsPerPageSetting, totalFiles);
|
const endIndex = Math.min(startIndex + itemsPerPageSetting, totalFiles);
|
||||||
@@ -270,7 +309,7 @@ export function renderFileTable(folder, container) {
|
|||||||
rowsHTML += "</tbody></table>";
|
rowsHTML += "</tbody></table>";
|
||||||
const bottomControlsHTML = buildBottomControls(itemsPerPageSetting);
|
const bottomControlsHTML = buildBottomControls(itemsPerPageSetting);
|
||||||
|
|
||||||
fileListContent.innerHTML = topControlsHTML + headerHTML + rowsHTML + bottomControlsHTML;
|
fileListContent.innerHTML = combinedTopHTML + headerHTML + rowsHTML + bottomControlsHTML;
|
||||||
|
|
||||||
createViewToggleButton();
|
createViewToggleButton();
|
||||||
|
|
||||||
@@ -492,4 +531,5 @@ window.changeItemsPerPage = function (newCount) {
|
|||||||
window.loadFileList = loadFileList;
|
window.loadFileList = loadFileList;
|
||||||
window.renderFileTable = renderFileTable;
|
window.renderFileTable = renderFileTable;
|
||||||
window.renderGalleryView = renderGalleryView;
|
window.renderGalleryView = renderGalleryView;
|
||||||
window.sortFiles = sortFiles;
|
window.sortFiles = sortFiles;
|
||||||
|
window.toggleAdvancedSearch = toggleAdvancedSearch;
|
||||||
Reference in New Issue
Block a user