video & pdf preview added
This commit is contained in:
114
fileManager.js
114
fileManager.js
@@ -126,7 +126,7 @@ export function loadFileList(folderParam) {
|
|||||||
// Debounce helper (if not defined already)
|
// Debounce helper (if not defined already)
|
||||||
function debounce(func, wait) {
|
function debounce(func, wait) {
|
||||||
let timeout;
|
let timeout;
|
||||||
return function(...args) {
|
return function (...args) {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
timeout = setTimeout(() => func.apply(this, args), wait);
|
||||||
};
|
};
|
||||||
@@ -137,21 +137,21 @@ export function renderFileTable(folder) {
|
|||||||
const folderPath = (folder === "root")
|
const folderPath = (folder === "root")
|
||||||
? "uploads/"
|
? "uploads/"
|
||||||
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
|
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
|
||||||
|
|
||||||
// Use the global search term if available.
|
// Use the global search term if available.
|
||||||
const searchTerm = window.currentSearchTerm || "";
|
const searchTerm = window.currentSearchTerm || "";
|
||||||
|
|
||||||
const filteredFiles = fileData.filter(file =>
|
const filteredFiles = fileData.filter(file =>
|
||||||
file.name.toLowerCase().includes(searchTerm.toLowerCase())
|
file.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get persistent items per page from localStorage.
|
// 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">
|
||||||
@@ -173,7 +173,7 @@ export function renderFileTable(folder) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
let tableHTML = `
|
let tableHTML = `
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -188,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);
|
||||||
@@ -201,15 +201,24 @@ 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");
|
||||||
|
|
||||||
const isImage = /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name);
|
const isViewable = /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic|pdf|mp4|webm|ogg)$/i.test(file.name);
|
||||||
|
let previewButton = "";
|
||||||
const previewButton = isImage
|
if (isViewable) {
|
||||||
? `<button class="btn btn-sm btn-info ml-2" onclick="event.stopPropagation(); previewImage('${folderPath + encodeURIComponent(file.name)}', '${safeFileName}')">
|
let previewIcon = "";
|
||||||
<i class="material-icons">image</i>
|
if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic)$/i.test(file.name)) {
|
||||||
</button>`
|
previewIcon = `<i class="material-icons">image</i>`;
|
||||||
: "";
|
} else if (/\.(mp4|webm|ogg)$/i.test(file.name)) {
|
||||||
|
previewIcon = `<i class="material-icons">videocam</i>`;
|
||||||
|
} else if (/\.pdf$/i.test(file.name)) {
|
||||||
|
previewIcon = `<i class="material-icons">picture_as_pdf</i>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewButton = `<button class="btn btn-sm btn-info ml-2 preview-btn" onclick="event.stopPropagation(); previewFile('${folderPath + encodeURIComponent(file.name)}', '${safeFileName}')">
|
||||||
|
${previewIcon}
|
||||||
|
</button>`;
|
||||||
|
}
|
||||||
|
|
||||||
tableBody += `
|
tableBody += `
|
||||||
<tr onclick="toggleRowSelection(event, '${safeFileName}')" class="clickable-row">
|
<tr onclick="toggleRowSelection(event, '${safeFileName}')" class="clickable-row">
|
||||||
<td>
|
<td>
|
||||||
@@ -235,21 +244,21 @@ 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;
|
||||||
|
|
||||||
// Re-attach event listener for the new search input element.
|
// Re-attach event listener for the new search input element.
|
||||||
const newSearchInput = document.getElementById("searchInput");
|
const newSearchInput = document.getElementById("searchInput");
|
||||||
if (newSearchInput) {
|
if (newSearchInput) {
|
||||||
@@ -267,7 +276,7 @@ export function renderFileTable(folder) {
|
|||||||
}, 0);
|
}, 0);
|
||||||
}, 300));
|
}, 300));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add event listeners for header sorting.
|
// 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 () {
|
||||||
@@ -275,7 +284,7 @@ export function renderFileTable(folder) {
|
|||||||
sortFiles(column, folder);
|
sortFiles(column, folder);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add event listeners for checkboxes.
|
// 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) {
|
||||||
@@ -283,17 +292,17 @@ export function renderFileTable(folder) {
|
|||||||
updateFileActionButtons();
|
updateFileActionButtons();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
updateFileActionButtons();
|
updateFileActionButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global function to show an image preview modal.
|
// Global function to show an image preview modal.
|
||||||
window.previewImage = function (imageUrl, fileName) {
|
window.previewFile = function (fileUrl, fileName) {
|
||||||
let modal = document.getElementById("imagePreviewModal");
|
let modal = document.getElementById("filePreviewModal");
|
||||||
if (!modal) {
|
if (!modal) {
|
||||||
modal = document.createElement("div");
|
modal = document.createElement("div");
|
||||||
modal.id = "imagePreviewModal";
|
modal.id = "filePreviewModal";
|
||||||
// Full-screen overlay using flexbox, with no padding.
|
// Use the same styling as the original image modal.
|
||||||
Object.assign(modal.style, {
|
Object.assign(modal.style, {
|
||||||
display: "none",
|
display: "none",
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
@@ -309,12 +318,12 @@ window.previewImage = function (imageUrl, fileName) {
|
|||||||
});
|
});
|
||||||
modal.innerHTML = `
|
modal.innerHTML = `
|
||||||
<div class="modal-content image-preview-modal-content">
|
<div class="modal-content image-preview-modal-content">
|
||||||
<span id="closeImageModal" class="close-image-modal">×</span>
|
<span id="closeFileModal" class="close-image-modal">×</span>
|
||||||
<h4 class="image-modal-header"></h4>
|
<h4 class="image-modal-header"></h4>
|
||||||
<img src="" class="image-modal-img" />
|
<div class="file-preview-container"></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
document.body.appendChild(modal);
|
document.body.appendChild(modal);
|
||||||
document.getElementById("closeImageModal").addEventListener("click", function () {
|
document.getElementById("closeFileModal").addEventListener("click", function () {
|
||||||
modal.style.display = "none";
|
modal.style.display = "none";
|
||||||
});
|
});
|
||||||
modal.addEventListener("click", function (e) {
|
modal.addEventListener("click", function (e) {
|
||||||
@@ -324,7 +333,38 @@ window.previewImage = function (imageUrl, fileName) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
modal.querySelector("h4").textContent = "Preview: " + fileName;
|
modal.querySelector("h4").textContent = "Preview: " + fileName;
|
||||||
modal.querySelector("img").src = imageUrl;
|
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 <embed> with explicit sizing
|
||||||
|
const embed = document.createElement("embed");
|
||||||
|
embed.src = fileUrl;
|
||||||
|
embed.type = "application/pdf";
|
||||||
|
// Instead of using the image-modal-img class, set larger dimensions
|
||||||
|
embed.style.width = "80vw";
|
||||||
|
embed.style.height = "80vh";
|
||||||
|
embed.style.border = "none";
|
||||||
|
container.appendChild(embed);
|
||||||
|
} else if (/\.(mp4|webm|ogg)$/i.test(fileName)) {
|
||||||
|
// Video preview using <video>
|
||||||
|
const video = document.createElement("video");
|
||||||
|
video.src = fileUrl;
|
||||||
|
video.controls = true;
|
||||||
|
video.className = "image-modal-img";
|
||||||
|
container.appendChild(video);
|
||||||
|
} else {
|
||||||
|
container.textContent = "Preview not available for this file type.";
|
||||||
|
}
|
||||||
|
|
||||||
modal.style.display = "flex";
|
modal.style.display = "flex";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -730,15 +770,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);
|
||||||
|
|
||||||
@@ -750,7 +790,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";
|
||||||
|
|||||||
12
styles.css
12
styles.css
@@ -1306,6 +1306,18 @@ body.dark-mode .image-preview-modal-content {
|
|||||||
border-color: #444;
|
border-color: #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-btn i.material-icons {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ensure the image resizes properly */
|
/* Ensure the image resizes properly */
|
||||||
.image-modal-img {
|
.image-modal-img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|||||||
Reference in New Issue
Block a user