search and drop fixes
This commit is contained in:
118
fileManager.js
118
fileManager.js
@@ -123,50 +123,57 @@ export function loadFileList(folderParam) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
export function renderFileTable(folder) {
|
||||||
const fileListContainer = document.getElementById("fileList");
|
const fileListContainer = document.getElementById("fileList");
|
||||||
const folderPath = (folder === "root")
|
const folderPath = (folder === "root")
|
||||||
? "uploads/"
|
? "uploads/"
|
||||||
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
|
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
|
||||||
|
|
||||||
// Attempt to get the search input element.
|
// Use the global search term if available.
|
||||||
const searchInputElement = document.getElementById("searchInput");
|
const searchTerm = window.currentSearchTerm || "";
|
||||||
const searchHadFocus = searchInputElement && (document.activeElement === searchInputElement);
|
|
||||||
const searchTerm = searchInputElement ? searchInputElement.value : "";
|
|
||||||
|
|
||||||
const filteredFiles = fileData.filter(file =>
|
const filteredFiles = fileData.filter(file =>
|
||||||
file.name.toLowerCase().includes(searchTerm.toLowerCase())
|
file.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
// Read persistent items per page from localStorage, default to 10.
|
// Get persistent items per page from localStorage.
|
||||||
const itemsPerPageSetting = parseInt(localStorage.getItem('itemsPerPage') || '10', 10);
|
const itemsPerPageSetting = parseInt(localStorage.getItem('itemsPerPage') || '10', 10);
|
||||||
const currentPage = window.currentPage || 1;
|
const currentPage = window.currentPage || 1;
|
||||||
const totalFiles = filteredFiles.length;
|
const totalFiles = filteredFiles.length;
|
||||||
const totalPages = Math.ceil(totalFiles / itemsPerPageSetting);
|
const totalPages = Math.ceil(totalFiles / itemsPerPageSetting);
|
||||||
const safeSearchTerm = escapeHTML(searchTerm);
|
const safeSearchTerm = escapeHTML(searchTerm);
|
||||||
|
|
||||||
const topControlsHTML = `
|
const topControlsHTML = `
|
||||||
<div class="row align-items-center mb-3">
|
<div class="row align-items-center mb-3">
|
||||||
<div class="col-12 col-md-8 mb-2 mb-md-0">
|
<div class="col-12 col-md-8 mb-2 mb-md-0">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text" id="searchIcon">
|
<span class="input-group-text" id="searchIcon">
|
||||||
<i class="material-icons">search</i>
|
<i class="material-icons">search</i>
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
<input type="text" id="searchInput" class="form-control" placeholder="Search files..." value="${safeSearchTerm}" aria-describedby="searchIcon">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4 text-left">
|
||||||
|
<div class="d-flex justify-content-center justify-content-md-start align-items-center">
|
||||||
|
<button class="custom-prev-next-btn" ${currentPage === 1 ? "disabled" : ""} onclick="changePage(${currentPage - 1})">Prev</button>
|
||||||
|
<span class="page-indicator">Page ${currentPage} of ${totalPages || 1}</span>
|
||||||
|
<button class="custom-prev-next-btn" ${currentPage === totalPages || totalFiles === 0 ? "disabled" : ""} onclick="changePage(${currentPage + 1})">Next</button>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="searchInput" class="form-control" placeholder="Search files..." value="${safeSearchTerm}" aria-describedby="searchIcon">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4 text-left">
|
|
||||||
<div class="d-flex justify-content-center justify-content-md-start align-items-center">
|
|
||||||
<button class="custom-prev-next-btn" ${currentPage === 1 ? "disabled" : ""} onclick="changePage(${currentPage - 1})">Prev</button>
|
|
||||||
<span class="page-indicator">Page ${currentPage} of ${totalPages || 1}</span>
|
|
||||||
<button class="custom-prev-next-btn" ${currentPage === totalPages || totalFiles === 0 ? "disabled" : ""} onclick="changePage(${currentPage + 1})">Next</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
let tableHTML = `
|
let tableHTML = `
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -181,11 +188,11 @@ export function renderFileTable(folder) {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const startIndex = (currentPage - 1) * itemsPerPageSetting;
|
const startIndex = (currentPage - 1) * itemsPerPageSetting;
|
||||||
const endIndex = Math.min(startIndex + itemsPerPageSetting, totalFiles);
|
const endIndex = Math.min(startIndex + itemsPerPageSetting, totalFiles);
|
||||||
let tableBody = `<tbody>`;
|
let tableBody = `<tbody>`;
|
||||||
|
|
||||||
if (totalFiles > 0) {
|
if (totalFiles > 0) {
|
||||||
filteredFiles.slice(startIndex, endIndex).forEach(file => {
|
filteredFiles.slice(startIndex, endIndex).forEach(file => {
|
||||||
const isEditable = canEditFile(file.name);
|
const isEditable = canEditFile(file.name);
|
||||||
@@ -194,16 +201,15 @@ export function renderFileTable(folder) {
|
|||||||
const safeUploaded = escapeHTML(file.uploaded);
|
const safeUploaded = escapeHTML(file.uploaded);
|
||||||
const safeSize = escapeHTML(file.size);
|
const safeSize = escapeHTML(file.size);
|
||||||
const safeUploader = escapeHTML(file.uploader || "Unknown");
|
const safeUploader = escapeHTML(file.uploader || "Unknown");
|
||||||
|
|
||||||
// Check if file is an image.
|
|
||||||
const isImage = /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name);
|
const isImage = /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name);
|
||||||
|
|
||||||
const previewButton = isImage
|
const previewButton = isImage
|
||||||
? `<button class="btn btn-sm btn-info ml-2" onclick="event.stopPropagation(); previewImage('${folderPath + encodeURIComponent(file.name)}', '${safeFileName}')">
|
? `<button class="btn btn-sm btn-info ml-2" onclick="event.stopPropagation(); previewImage('${folderPath + encodeURIComponent(file.name)}', '${safeFileName}')">
|
||||||
<i class="material-icons">image</i>
|
<i class="material-icons">image</i>
|
||||||
</button>`
|
</button>`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
tableBody += `
|
tableBody += `
|
||||||
<tr onclick="toggleRowSelection(event, '${safeFileName}')" class="clickable-row">
|
<tr onclick="toggleRowSelection(event, '${safeFileName}')" class="clickable-row">
|
||||||
<td>
|
<td>
|
||||||
@@ -229,43 +235,55 @@ export function renderFileTable(folder) {
|
|||||||
tableBody += `<tr><td colspan="7">No files found.</td></tr>`;
|
tableBody += `<tr><td colspan="7">No files found.</td></tr>`;
|
||||||
}
|
}
|
||||||
tableBody += `</tbody></table>`;
|
tableBody += `</tbody></table>`;
|
||||||
|
|
||||||
const bottomControlsHTML = `
|
const bottomControlsHTML = `
|
||||||
<div class="d-flex align-items-center mt-3 bottom-controls">
|
<div class="d-flex align-items-center mt-3 bottom-controls">
|
||||||
<label class="label-inline mr-2 mb-0">Show</label>
|
<label class="label-inline mr-2 mb-0">Show</label>
|
||||||
<select class="form-control bottom-select" onchange="changeItemsPerPage(this.value)">
|
<select class="form-control bottom-select" onchange="changeItemsPerPage(this.value)">
|
||||||
${[10, 20, 50, 100]
|
${[10, 20, 50, 100]
|
||||||
.map(num => `<option value="${num}" ${num === itemsPerPageSetting ? "selected" : ""}>${num}</option>`)
|
.map(num => `<option value="${num}" ${num === itemsPerPageSetting ? "selected" : ""}>${num}</option>`)
|
||||||
.join("")}
|
.join("")}
|
||||||
</select>
|
</select>
|
||||||
<span class="items-per-page-text ml-2 mb-0">items per page</span>
|
<span class="items-per-page-text ml-2 mb-0">items per page</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
fileListContainer.innerHTML = topControlsHTML + tableHTML + tableBody + bottomControlsHTML;
|
fileListContainer.innerHTML = topControlsHTML + tableHTML + tableBody + bottomControlsHTML;
|
||||||
|
|
||||||
// Only add event listeners if searchInputElement exists.
|
// Re-attach event listener for the new search input element.
|
||||||
if (searchInputElement) {
|
const newSearchInput = document.getElementById("searchInput");
|
||||||
searchInputElement.addEventListener("input", function () {
|
if (newSearchInput) {
|
||||||
|
newSearchInput.addEventListener("input", debounce(function () {
|
||||||
|
window.currentSearchTerm = newSearchInput.value;
|
||||||
window.currentPage = 1;
|
window.currentPage = 1;
|
||||||
renderFileTable(folder);
|
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 => {
|
document.querySelectorAll("table.table thead th[data-column]").forEach(cell => {
|
||||||
cell.addEventListener("click", function () {
|
cell.addEventListener("click", function () {
|
||||||
const column = this.getAttribute("data-column");
|
const column = this.getAttribute("data-column");
|
||||||
sortFiles(column, folder);
|
sortFiles(column, folder);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add event listeners for checkboxes.
|
||||||
document.querySelectorAll('#fileList .file-checkbox').forEach(checkbox => {
|
document.querySelectorAll('#fileList .file-checkbox').forEach(checkbox => {
|
||||||
checkbox.addEventListener('change', function (e) {
|
checkbox.addEventListener('change', function (e) {
|
||||||
updateRowHighlight(e.target);
|
updateRowHighlight(e.target);
|
||||||
updateFileActionButtons();
|
updateFileActionButtons();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
updateFileActionButtons();
|
updateFileActionButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,15 +730,15 @@ export function editFile(fileName, folder) {
|
|||||||
theme: theme,
|
theme: theme,
|
||||||
viewportMargin: Infinity
|
viewportMargin: Infinity
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure height adjustment
|
// Ensure height adjustment
|
||||||
window.currentEditor = editor;
|
window.currentEditor = editor;
|
||||||
|
|
||||||
// Adjust height AFTER modal appears
|
// Adjust height AFTER modal appears
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
adjustEditorSize(); // Set initial height
|
adjustEditorSize(); // Set initial height
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
// Attach modal resize observer
|
// Attach modal resize observer
|
||||||
observeModalResize(modal);
|
observeModalResize(modal);
|
||||||
|
|
||||||
@@ -732,7 +750,7 @@ export function editFile(fileName, folder) {
|
|||||||
document.getElementById("closeEditorX").addEventListener("click", function () {
|
document.getElementById("closeEditorX").addEventListener("click", function () {
|
||||||
modal.remove();
|
modal.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("decreaseFont").addEventListener("click", function () {
|
document.getElementById("decreaseFont").addEventListener("click", function () {
|
||||||
currentFontSize = Math.max(8, currentFontSize - 2);
|
currentFontSize = Math.max(8, currentFontSize - 2);
|
||||||
editor.getWrapperElement().style.fontSize = currentFontSize + "px";
|
editor.getWrapperElement().style.fontSize = currentFontSize + "px";
|
||||||
|
|||||||
50
upload.js
50
upload.js
@@ -45,6 +45,29 @@ function getFilesFromDataTransferItems(items) {
|
|||||||
return Promise.all(promises).then(results => results.flat());
|
return Promise.all(promises).then(results => results.flat());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: Set default drop area content.
|
||||||
|
// Moved to module scope so it is available globally in this module.
|
||||||
|
function setDropAreaDefault() {
|
||||||
|
const dropArea = document.getElementById("uploadDropArea");
|
||||||
|
if (dropArea) {
|
||||||
|
dropArea.innerHTML = `
|
||||||
|
<div id="uploadInstruction" class="upload-instruction">
|
||||||
|
Drop files/folders here or click 'Choose files'
|
||||||
|
</div>
|
||||||
|
<div id="uploadFileRow" class="upload-file-row">
|
||||||
|
<button id="customChooseBtn" type="button">
|
||||||
|
Choose files
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="fileInfoWrapper" class="file-info-wrapper">
|
||||||
|
<div id="fileInfoContainer" class="file-info-container">
|
||||||
|
<span id="fileInfoDefault">No files selected</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function adjustFolderHelpExpansion() {
|
function adjustFolderHelpExpansion() {
|
||||||
const uploadCard = document.getElementById("uploadCard");
|
const uploadCard = document.getElementById("uploadCard");
|
||||||
const folderHelpDetails = document.querySelector(".folder-help-details");
|
const folderHelpDetails = document.querySelector(".folder-help-details");
|
||||||
@@ -247,8 +270,8 @@ function processFiles(filesInput) {
|
|||||||
}
|
}
|
||||||
const listWrapper = document.createElement("div");
|
const listWrapper = document.createElement("div");
|
||||||
listWrapper.classList.add("upload-progress-wrapper");
|
listWrapper.classList.add("upload-progress-wrapper");
|
||||||
// Set a maximum height (adjust as needed) and enable vertical scrolling.
|
// Set a maximum height and enable vertical scrolling.
|
||||||
listWrapper.style.maxHeight = "300px"; // for example, 300px
|
listWrapper.style.maxHeight = "300px";
|
||||||
listWrapper.style.overflowY = "auto";
|
listWrapper.style.overflowY = "auto";
|
||||||
listWrapper.appendChild(list);
|
listWrapper.appendChild(list);
|
||||||
progressContainer.appendChild(listWrapper);
|
progressContainer.appendChild(listWrapper);
|
||||||
@@ -433,30 +456,11 @@ function initUpload() {
|
|||||||
fileInput.setAttribute("directory", "");
|
fileInput.setAttribute("directory", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Set default drop area content.
|
// Set default drop area content.
|
||||||
function setDropAreaDefault() {
|
setDropAreaDefault();
|
||||||
if (dropArea) {
|
|
||||||
dropArea.innerHTML = `
|
|
||||||
<div id="uploadInstruction" class="upload-instruction">
|
|
||||||
Drop files/folders here or click 'Choose files'
|
|
||||||
</div>
|
|
||||||
<div id="uploadFileRow" class="upload-file-row">
|
|
||||||
<button id="customChooseBtn" type="button">
|
|
||||||
Choose files
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="fileInfoWrapper" class="file-info-wrapper">
|
|
||||||
<div id="fileInfoContainer" class="file-info-container">
|
|
||||||
<span id="fileInfoDefault">No files selected</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dropArea) {
|
if (dropArea) {
|
||||||
dropArea.classList.add("upload-drop-area");
|
dropArea.classList.add("upload-drop-area");
|
||||||
setDropAreaDefault();
|
|
||||||
dropArea.addEventListener("dragover", function (e) {
|
dropArea.addEventListener("dragover", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// Use a darker color if dark mode is active.
|
// Use a darker color if dark mode is active.
|
||||||
|
|||||||
Reference in New Issue
Block a user