css refactoring & auto dark/light mode
This commit is contained in:
12
README.md
12
README.md
@@ -1,7 +1,11 @@
|
|||||||
# Multi File Upload Editor
|
# Multi File Upload Editor
|
||||||
|
|
||||||
|
**Light mode**
|
||||||

|

|
||||||
|
|
||||||
|
**Dark mode**
|
||||||
|

|
||||||
|
|
||||||
changelogs available here: <https://github.com/error311/multi-file-upload-editor-docker/>
|
changelogs available here: <https://github.com/error311/multi-file-upload-editor-docker/>
|
||||||
|
|
||||||
Multi File Upload Editor is a lightweight, secure web application for uploading, editing, and managing files. It’s built with an Apache/PHP backend and a modern JavaScript frontend (ES6 modules) to provide a responsive, dynamic file management interface. The application is ideal for scenarios like document management, image galleries, firmware file hosting, or any situation where multiple files need to be uploaded and organized through a web interface.
|
Multi File Upload Editor is a lightweight, secure web application for uploading, editing, and managing files. It’s built with an Apache/PHP backend and a modern JavaScript frontend (ES6 modules) to provide a responsive, dynamic file management interface. The application is ideal for scenarios like document management, image galleries, firmware file hosting, or any situation where multiple files need to be uploaded and organized through a web interface.
|
||||||
@@ -27,6 +31,14 @@ Multi File Upload Editor is a lightweight, secure web application for uploading,
|
|||||||
- Secure, session-based authentication protects the editor. An admin user can add or remove users through the interface. Passwords are hashed using PHP’s password_hash() for security, and session checks prevent unauthorized access to backend endpoints.
|
- Secure, session-based authentication protects the editor. An admin user can add or remove users through the interface. Passwords are hashed using PHP’s password_hash() for security, and session checks prevent unauthorized access to backend endpoints.
|
||||||
- **Responsive, Dynamic UI:**
|
- **Responsive, Dynamic UI:**
|
||||||
- The interface is mobile-friendly and adjusts to different screen sizes (hiding non-critical columns on small devices to avoid clutter). Updates to the file list, folder tree, and upload progress happen asynchronously (via Fetch API and XMLHttpRequest), so the page never needs to fully reload. Users receive immediate feedback through toast notifications and modal dialogs for actions like confirmations and error messages, creating a smooth user experience.
|
- The interface is mobile-friendly and adjusts to different screen sizes (hiding non-critical columns on small devices to avoid clutter). Updates to the file list, folder tree, and upload progress happen asynchronously (via Fetch API and XMLHttpRequest), so the page never needs to fully reload. Users receive immediate feedback through toast notifications and modal dialogs for actions like confirmations and error messages, creating a smooth user experience.
|
||||||
|
- **Dark Mode/Light Mode**
|
||||||
|
- Automatically adapts to the operating system’s theme preference by default, with a manual toggle option.
|
||||||
|
- A theme toggle allows users to switch between Dark Mode and Light Mode for an optimized viewing experience.
|
||||||
|
- The selected mode persists across sessions, ensuring the theme remains active even after a page refresh.
|
||||||
|
- Every element, including the header, buttons, tables, modals, and the file editor, dynamically adapts to the selected theme.
|
||||||
|
- Dark Mode: Uses a dark gray background with lighter text to reduce eye strain in low-light environments.
|
||||||
|
- Light Mode: Retains the classic bright interface for high visibility in well-lit conditions.
|
||||||
|
- CodeMirror editor applies a matching dark theme in Dark Mode for better readability when editing files.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
122
fileManager.js
122
fileManager.js
@@ -133,7 +133,7 @@ export function renderFileTable(folder) {
|
|||||||
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" style="max-width: 100%;">
|
<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>
|
||||||
@@ -145,7 +145,7 @@ export function renderFileTable(folder) {
|
|||||||
<div class="col-12 col-md-4 text-left">
|
<div class="col-12 col-md-4 text-left">
|
||||||
<div class="d-flex justify-content-center justify-content-md-start align-items-center">
|
<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>
|
<button class="custom-prev-next-btn" ${currentPage === 1 ? "disabled" : ""} onclick="changePage(${currentPage - 1})">Prev</button>
|
||||||
<span style="margin: 0 8px; white-space: nowrap;">Page ${currentPage} of ${totalPages || 1}</span>
|
<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>
|
<button class="custom-prev-next-btn" ${currentPage === totalPages || totalFiles === 0 ? "disabled" : ""} onclick="changePage(${currentPage + 1})">Next</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -156,12 +156,12 @@ export function renderFileTable(folder) {
|
|||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 40px;"><input type="checkbox" id="selectAll" onclick="toggleAllCheckboxes(this)"></th>
|
<th class="checkbox-col"><input type="checkbox" id="selectAll" onclick="toggleAllCheckboxes(this)"></th>
|
||||||
<th data-column="name" style="cursor:pointer; white-space: nowrap;">File Name ${sortOrder.column === "name" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
<th data-column="name" class="sortable-col">File Name ${sortOrder.column === "name" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
||||||
<th data-column="modified" class="hide-small" style="cursor:pointer; white-space: nowrap;">Date Modified ${sortOrder.column === "modified" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
<th data-column="modified" class="hide-small sortable-col">Date Modified ${sortOrder.column === "modified" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
||||||
<th data-column="uploaded" class="hide-small hide-medium" style="cursor:pointer; white-space: nowrap;">Upload Date ${sortOrder.column === "uploaded" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
<th data-column="uploaded" class="hide-small hide-medium sortable-col">Upload Date ${sortOrder.column === "uploaded" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
||||||
<th data-column="size" class="hide-small" style="cursor:pointer; white-space: nowrap;">File Size ${sortOrder.column === "size" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
<th data-column="size" class="hide-small sortable-col">File Size ${sortOrder.column === "size" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
||||||
<th data-column="uploader" class="hide-small hide-medium" style="cursor:pointer; white-space: nowrap;">Uploader ${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
<th data-column="uploader" class="hide-small hide-medium sortable-col">Uploader ${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -185,21 +185,21 @@ export function renderFileTable(folder) {
|
|||||||
|
|
||||||
// Build the preview button HTML string using the file's properties directly.
|
// Build the preview button HTML string using the file's properties directly.
|
||||||
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}')" style="cursor:pointer;">
|
<tr onclick="toggleRowSelection(event, '${safeFileName}')" class="clickable-row">
|
||||||
<td>
|
<td>
|
||||||
<input type="checkbox" class="file-checkbox" value="${safeFileName}" onclick="event.stopPropagation(); updateRowHighlight(this);">
|
<input type="checkbox" class="file-checkbox" value="${safeFileName}" onclick="event.stopPropagation(); updateRowHighlight(this);">
|
||||||
</td>
|
</td>
|
||||||
<td>${safeFileName}</td>
|
<td>${safeFileName}</td>
|
||||||
<td class="hide-small" style="white-space: nowrap;">${safeModified}</td>
|
<td class="hide-small nowrap">${safeModified}</td>
|
||||||
<td class="hide-small hide-medium" style="white-space: nowrap;">${safeUploaded}</td>
|
<td class="hide-small hide-medium nowrap">${safeUploaded}</td>
|
||||||
<td class="hide-small" style="white-space: nowrap;">${safeSize}</td>
|
<td class="hide-small nowrap">${safeSize}</td>
|
||||||
<td class="hide-small hide-medium" style="white-space: nowrap;">${safeUploader}</td>
|
<td class="hide-small hide-medium nowrap">${safeUploader}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="button-wrap">
|
<div class="button-wrap">
|
||||||
<a class="btn btn-sm btn-success" href="${folderPath + encodeURIComponent(file.name)}" download>Download</a>
|
<a class="btn btn-sm btn-success" href="${folderPath + encodeURIComponent(file.name)}" download>Download</a>
|
||||||
@@ -217,12 +217,12 @@ export function renderFileTable(folder) {
|
|||||||
tableBody += `</tbody></table>`;
|
tableBody += `</tbody></table>`;
|
||||||
|
|
||||||
const bottomControlsHTML = `
|
const bottomControlsHTML = `
|
||||||
<div class="d-flex align-items-center mt-3" style="font-size:16px; line-height:1.5;">
|
<div class="d-flex align-items-center mt-3 bottom-controls">
|
||||||
<label class="mr-2 mb-0" style="font-size:16px; line-height:1.5;">Show</label>
|
<label class="label-inline mr-2 mb-0">Show</label>
|
||||||
<select class="form-control" style="width:auto; font-size:16px; height:auto;" onchange="changeItemsPerPage(this.value)">
|
<select class="form-control bottom-select" onchange="changeItemsPerPage(this.value)">
|
||||||
${[10, 20, 50, 100].map(num => `<option value="${num}" ${num === itemsPerPage ? "selected" : ""}>${num}</option>`).join("")}
|
${[10, 20, 50, 100].map(num => `<option value="${num}" ${num === itemsPerPage ? "selected" : ""}>${num}</option>`).join("")}
|
||||||
</select>
|
</select>
|
||||||
<span class="ml-2 mb-0" style="font-size:16px; line-height:1.5;">items per page</span>
|
<span class="items-per-page-text ml-2 mb-0">items per page</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -274,10 +274,10 @@ window.previewImage = function (imageUrl, fileName) {
|
|||||||
zIndex: "1000"
|
zIndex: "1000"
|
||||||
});
|
});
|
||||||
modal.innerHTML = `
|
modal.innerHTML = `
|
||||||
<div class="modal-content" style="max-width: 90vw; max-height: 90vh; background: white; padding: 20px; border-radius: 4px; overflow: auto; margin: auto; position: relative;">
|
<div class="modal-content image-preview-modal-content">
|
||||||
<span id="closeImageModal" style="position: absolute; top: 10px; right: 20px; font-size: 28px; cursor: pointer;">×</span>
|
<span id="closeImageModal" class="close-image-modal">×</span>
|
||||||
<h4 style="text-align: center; margin: 0 0 10px;"></h4>
|
<h4 class="image-modal-header"></h4>
|
||||||
<img src="" style="max-width: 100%; max-height: 80vh; object-fit: contain; display: block; margin: 0 auto;" />
|
<img src="" class="image-modal-img" />
|
||||||
</div>`;
|
</div>`;
|
||||||
document.body.appendChild(modal);
|
document.body.appendChild(modal);
|
||||||
document.getElementById("closeImageModal").addEventListener("click", function () {
|
document.getElementById("closeImageModal").addEventListener("click", function () {
|
||||||
@@ -602,8 +602,13 @@ function getModeForFile(fileName) {
|
|||||||
|
|
||||||
export function editFile(fileName, folder) {
|
export function editFile(fileName, folder) {
|
||||||
console.log("Edit button clicked for:", fileName);
|
console.log("Edit button clicked for:", fileName);
|
||||||
|
|
||||||
|
// Remove any existing editor modal before creating a new one
|
||||||
let existingEditor = document.getElementById("editorContainer");
|
let existingEditor = document.getElementById("editorContainer");
|
||||||
if (existingEditor) { existingEditor.remove(); }
|
if (existingEditor) {
|
||||||
|
existingEditor.remove();
|
||||||
|
}
|
||||||
|
|
||||||
const folderUsed = folder || window.currentFolder || "root";
|
const folderUsed = folder || window.currentFolder || "root";
|
||||||
const folderPath = (folderUsed === "root")
|
const folderPath = (folderUsed === "root")
|
||||||
? "uploads/"
|
? "uploads/"
|
||||||
@@ -614,8 +619,9 @@ export function editFile(fileName, folder) {
|
|||||||
.then(response => {
|
.then(response => {
|
||||||
const contentLength = response.headers.get("Content-Length");
|
const contentLength = response.headers.get("Content-Length");
|
||||||
console.log("Content-Length:", contentLength);
|
console.log("Content-Length:", contentLength);
|
||||||
// If header is missing or file size exceeds threshold, block editing.
|
|
||||||
if (contentLength === null || parseInt(contentLength) > 10485760) {
|
// Block editing if file size exceeds 10MB or Content-Length is missing
|
||||||
|
if (!contentLength || parseInt(contentLength) > 10485760) {
|
||||||
showToast("This file is larger than 10 MB and cannot be edited in the browser.");
|
showToast("This file is larger than 10 MB and cannot be edited in the browser.");
|
||||||
throw new Error("File too large.");
|
throw new Error("File too large.");
|
||||||
}
|
}
|
||||||
@@ -628,55 +634,76 @@ export function editFile(fileName, folder) {
|
|||||||
return response.text();
|
return response.text();
|
||||||
})
|
})
|
||||||
.then(content => {
|
.then(content => {
|
||||||
|
// Create the editor modal
|
||||||
const modal = document.createElement("div");
|
const modal = document.createElement("div");
|
||||||
modal.id = "editorContainer";
|
modal.id = "editorContainer";
|
||||||
modal.classList.add("modal", "editor-modal");
|
modal.classList.add("modal", "editor-modal");
|
||||||
modal.innerHTML = `
|
modal.innerHTML = `
|
||||||
<h3>Editing: ${fileName}</h3>
|
<h3 class="editor-title">Editing: ${fileName}</h3>
|
||||||
<div id="editorControls" style="text-align:right; margin-bottom:5px;">
|
<div id="editorControls" class="editor-controls">
|
||||||
<button id="decreaseFont" class="btn btn-sm btn-secondary">A-</button>
|
<button id="decreaseFont" class="btn btn-sm btn-secondary">A-</button>
|
||||||
<button id="increaseFont" class="btn btn-sm btn-secondary">A+</button>
|
<button id="increaseFont" class="btn btn-sm btn-secondary">A+</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea id="fileEditor" style="width:100%; height:60%; resize:none;">${content}</textarea>
|
<textarea id="fileEditor" class="editor-textarea">${content}</textarea>
|
||||||
<div style="margin-top:10px; text-align:right;">
|
<div class="editor-footer">
|
||||||
<button id="saveBtn" class="btn btn-primary">Save</button>
|
<button id="saveBtn" class="btn btn-primary">Save</button>
|
||||||
<button id="closeBtn" class="btn btn-secondary">Close</button>
|
<button id="closeBtn" class="btn btn-secondary">Close</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
document.body.appendChild(modal);
|
document.body.appendChild(modal);
|
||||||
modal.style.display = "block";
|
modal.style.display = "block";
|
||||||
|
|
||||||
|
// Determine file mode and set CodeMirror editor
|
||||||
const mode = getModeForFile(fileName);
|
const mode = getModeForFile(fileName);
|
||||||
|
const isDarkMode = document.body.classList.contains("dark-mode");
|
||||||
|
const theme = isDarkMode ? "material-darker" : "default";
|
||||||
|
|
||||||
const editor = CodeMirror.fromTextArea(document.getElementById("fileEditor"), {
|
const editor = CodeMirror.fromTextArea(document.getElementById("fileEditor"), {
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
mode: mode,
|
mode: mode,
|
||||||
theme: "default",
|
theme: theme,
|
||||||
viewportMargin: Infinity
|
viewportMargin: Infinity
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.setSize("100%", "60vh");
|
editor.setSize("100%", "60vh");
|
||||||
window.currentEditor = editor;
|
window.currentEditor = editor;
|
||||||
|
|
||||||
|
// Font size control
|
||||||
let currentFontSize = 14;
|
let currentFontSize = 14;
|
||||||
editor.getWrapperElement().style.fontSize = currentFontSize + "px";
|
editor.getWrapperElement().style.fontSize = currentFontSize + "px";
|
||||||
editor.refresh();
|
editor.refresh();
|
||||||
|
|
||||||
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";
|
||||||
editor.refresh();
|
editor.refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("increaseFont").addEventListener("click", function() {
|
document.getElementById("increaseFont").addEventListener("click", function () {
|
||||||
currentFontSize = Math.min(32, currentFontSize + 2);
|
currentFontSize = Math.min(32, currentFontSize + 2);
|
||||||
editor.getWrapperElement().style.fontSize = currentFontSize + "px";
|
editor.getWrapperElement().style.fontSize = currentFontSize + "px";
|
||||||
editor.refresh();
|
editor.refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("saveBtn").addEventListener("click", function() {
|
// Save function
|
||||||
|
document.getElementById("saveBtn").addEventListener("click", function () {
|
||||||
saveFile(fileName, folderUsed);
|
saveFile(fileName, folderUsed);
|
||||||
});
|
});
|
||||||
document.getElementById("closeBtn").addEventListener("click", function() {
|
|
||||||
|
// Close function
|
||||||
|
document.getElementById("closeBtn").addEventListener("click", function () {
|
||||||
modal.remove();
|
modal.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Function to update the editor theme when dark mode is toggled
|
||||||
|
function updateEditorTheme() {
|
||||||
|
const isDarkMode = document.body.classList.contains("dark-mode");
|
||||||
|
editor.setOption("theme", isDarkMode ? "material-darker" : "default");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for dark mode toggle and update the theme dynamically
|
||||||
|
document.getElementById("darkModeToggle").addEventListener("click", updateEditorTheme);
|
||||||
})
|
})
|
||||||
.catch(error => console.error("Error loading file:", error));
|
.catch(error => console.error("Error loading file:", error));
|
||||||
}
|
}
|
||||||
@@ -714,19 +741,12 @@ export function displayFilePreview(file, container) {
|
|||||||
if (file.type.startsWith("image/")) {
|
if (file.type.startsWith("image/")) {
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
img.src = URL.createObjectURL(file);
|
img.src = URL.createObjectURL(file);
|
||||||
img.style.maxWidth = "100px";
|
img.classList.add("file-preview-img");
|
||||||
img.style.maxHeight = "100px";
|
|
||||||
img.style.marginRight = "5px";
|
|
||||||
img.style.marginLeft = "0px";
|
|
||||||
container.appendChild(img);
|
container.appendChild(img);
|
||||||
} else {
|
} else {
|
||||||
const iconSpan = document.createElement("span");
|
const iconSpan = document.createElement("span");
|
||||||
iconSpan.classList.add("material-icons");
|
iconSpan.classList.add("material-icons", "file-icon");
|
||||||
iconSpan.style.color = "#333";
|
|
||||||
iconSpan.textContent = "insert_drive_file";
|
iconSpan.textContent = "insert_drive_file";
|
||||||
iconSpan.style.marginRight = "0px";
|
|
||||||
iconSpan.style.marginLeft = "0px";
|
|
||||||
iconSpan.style.fontSize = "32px";
|
|
||||||
container.appendChild(iconSpan);
|
container.appendChild(iconSpan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
101
folderManager.js
101
folderManager.js
@@ -1,5 +1,6 @@
|
|||||||
import { loadFileList } from './fileManager.js';
|
import { loadFileList } from './fileManager.js';
|
||||||
import { showToast } from './domUtils.js';
|
import { showToast } from './domUtils.js';
|
||||||
|
|
||||||
// ----------------------
|
// ----------------------
|
||||||
// Helper functions
|
// Helper functions
|
||||||
// ----------------------
|
// ----------------------
|
||||||
@@ -35,26 +36,26 @@ function buildFolderTree(folders) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the folder tree as nested <ul> elements with toggle icons.
|
* Render the folder tree as nested <ul> elements using CSS classes.
|
||||||
* @param {object} tree - The tree object.
|
* @param {object} tree - The tree object.
|
||||||
* @param {string} parentPath - The path prefix.
|
* @param {string} parentPath - The path prefix.
|
||||||
* @param {string} defaultDisplay - "block" (open) or "none" (collapsed)
|
* @param {string} defaultDisplay - "block" (open) or "none" (collapsed)
|
||||||
*/
|
*/
|
||||||
function renderFolderTree(tree, parentPath = "", defaultDisplay = "none") {
|
function renderFolderTree(tree, parentPath = "", defaultDisplay = "block") {
|
||||||
let html = `<ul style="list-style-type:none; padding-left:20px; margin:0; display:${defaultDisplay};">`;
|
// Determine display class based on defaultDisplay value.
|
||||||
|
const displayClass = defaultDisplay === 'none' ? 'collapsed' : 'expanded';
|
||||||
|
let html = `<ul class="folder-tree ${displayClass}">`;
|
||||||
for (const folder in tree) {
|
for (const folder in tree) {
|
||||||
const fullPath = parentPath ? parentPath + "/" + folder : folder;
|
const fullPath = parentPath ? parentPath + "/" + folder : folder;
|
||||||
const hasChildren = Object.keys(tree[folder]).length > 0;
|
const hasChildren = Object.keys(tree[folder]).length > 0;
|
||||||
html += `<li style="margin:4px 0; display:block;">`;
|
html += `<li class="folder-item">`;
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
// For nested levels (below root) default to collapsed: toggle label "[+]"
|
html += `<span class="folder-toggle">[+]</span>`;
|
||||||
html += `<span class="folder-toggle" style="cursor:pointer; margin-right:5px;">[+]</span>`;
|
|
||||||
} else {
|
} else {
|
||||||
html += `<span style="display:inline-block; width:18px;"></span>`;
|
html += `<span class="folder-indent-placeholder"></span>`;
|
||||||
}
|
}
|
||||||
html += `<span class="folder-option" data-folder="${fullPath}" style="cursor:pointer;">${folder}</span>`;
|
html += `<span class="folder-option" data-folder="${fullPath}">${folder}</span>`;
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
// Nested children always collapse by default.
|
|
||||||
html += renderFolderTree(tree[folder], fullPath, "none");
|
html += renderFolderTree(tree[folder], fullPath, "none");
|
||||||
}
|
}
|
||||||
html += `</li>`;
|
html += `</li>`;
|
||||||
@@ -76,8 +77,9 @@ function expandTreePath(path) {
|
|||||||
if (option) {
|
if (option) {
|
||||||
const li = option.parentNode;
|
const li = option.parentNode;
|
||||||
const nestedUl = li.querySelector("ul");
|
const nestedUl = li.querySelector("ul");
|
||||||
if (nestedUl && (nestedUl.style.display === "none" || nestedUl.style.display === "")) {
|
if (nestedUl && (nestedUl.classList.contains("collapsed") || !nestedUl.classList.contains("expanded"))) {
|
||||||
nestedUl.style.display = "block";
|
nestedUl.classList.remove("collapsed");
|
||||||
|
nestedUl.classList.add("expanded");
|
||||||
const toggle = li.querySelector(".folder-toggle");
|
const toggle = li.querySelector(".folder-toggle");
|
||||||
if (toggle) {
|
if (toggle) {
|
||||||
toggle.textContent = "[-]";
|
toggle.textContent = "[-]";
|
||||||
@@ -95,34 +97,44 @@ export async function loadFolderTree(selectedFolder) {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch('getFolderList.php');
|
const response = await fetch('getFolderList.php');
|
||||||
|
|
||||||
// Check for Unauthorized status
|
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
console.error("Unauthorized: Please log in to view folders.");
|
console.error("Unauthorized: Please log in to view folders.");
|
||||||
// Optionally, redirect to the login page:
|
|
||||||
// window.location.href = "/login.html";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = await response.json();
|
const folders = await response.json();
|
||||||
|
console.log("Fetched folders:", folders);
|
||||||
if (!Array.isArray(folders)) {
|
if (!Array.isArray(folders)) {
|
||||||
console.error("Folder list response is not an array:", folders);
|
console.error("Folder list response is not an array:", folders);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = document.getElementById("folderTreeContainer");
|
const container = document.getElementById("folderTreeContainer");
|
||||||
if (!container) return;
|
if (!container) {
|
||||||
|
console.error("Folder tree container not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const tree = buildFolderTree(folders);
|
let html = "";
|
||||||
|
// Build the root row without inline styles.
|
||||||
// Build the root row.
|
html += `<div id="rootRow" class="root-row">
|
||||||
let html = `<div id="rootRow" style="margin-bottom:10px; display:flex; align-items:center;">`;
|
<span class="folder-toggle">[-]</span>
|
||||||
html += `<span class="folder-toggle" style="cursor:pointer; margin-right:5px;">[-]</span>`;
|
<span class="folder-option root-folder-option" data-folder="root">(Root)</span>
|
||||||
html += `<span class="folder-option" data-folder="root" style="cursor:pointer; font-weight:bold;">(Root)</span>`;
|
</div>`;
|
||||||
html += `</div>`;
|
// If no folders exist (empty array), render a default tree with just (Root).
|
||||||
// Append the nested tree for root. Force its display to "block".
|
if (folders.length === 0) {
|
||||||
html += renderFolderTree(tree, "", "block");
|
html += `<ul class="folder-tree expanded">
|
||||||
|
<li class="folder-item">
|
||||||
|
<span class="folder-option" data-folder="root">(Root)</span>
|
||||||
|
</li>
|
||||||
|
</ul>`;
|
||||||
|
} else {
|
||||||
|
const tree = buildFolderTree(folders);
|
||||||
|
html += renderFolderTree(tree, "", "block");
|
||||||
|
}
|
||||||
|
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
|
console.log("Rendered folder tree HTML:", container.innerHTML);
|
||||||
|
|
||||||
if (selectedFolder) {
|
if (selectedFolder) {
|
||||||
window.currentFolder = selectedFolder;
|
window.currentFolder = selectedFolder;
|
||||||
@@ -138,9 +150,9 @@ export async function loadFolderTree(selectedFolder) {
|
|||||||
expandTreePath(window.currentFolder);
|
expandTreePath(window.currentFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Attach events ---
|
// Attach events to folder options.
|
||||||
container.querySelectorAll(".folder-option").forEach(el => {
|
container.querySelectorAll(".folder-option").forEach(el => {
|
||||||
el.addEventListener("click", function(e) {
|
el.addEventListener("click", function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
container.querySelectorAll(".folder-option").forEach(item => item.classList.remove("selected"));
|
container.querySelectorAll(".folder-option").forEach(item => item.classList.remove("selected"));
|
||||||
this.classList.add("selected");
|
this.classList.add("selected");
|
||||||
@@ -152,33 +164,39 @@ export async function loadFolderTree(selectedFolder) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Attach toggle event for the root toggle.
|
||||||
const rootToggle = container.querySelector("#rootRow .folder-toggle");
|
const rootToggle = container.querySelector("#rootRow .folder-toggle");
|
||||||
if (rootToggle) {
|
if (rootToggle) {
|
||||||
rootToggle.addEventListener("click", function(e) {
|
rootToggle.addEventListener("click", function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const nestedUl = container.querySelector("#rootRow + ul");
|
const nestedUl = container.querySelector("#rootRow + ul");
|
||||||
if (nestedUl) {
|
if (nestedUl) {
|
||||||
if (nestedUl.style.display === "none" || nestedUl.style.display === "") {
|
if (nestedUl.classList.contains("collapsed") || !nestedUl.classList.contains("expanded")) {
|
||||||
nestedUl.style.display = "block";
|
nestedUl.classList.remove("collapsed");
|
||||||
|
nestedUl.classList.add("expanded");
|
||||||
this.textContent = "[-]";
|
this.textContent = "[-]";
|
||||||
} else {
|
} else {
|
||||||
nestedUl.style.display = "none";
|
nestedUl.classList.remove("expanded");
|
||||||
|
nestedUl.classList.add("collapsed");
|
||||||
this.textContent = "[+]";
|
this.textContent = "[+]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attach toggle events for all folder toggles.
|
||||||
container.querySelectorAll(".folder-toggle").forEach(toggle => {
|
container.querySelectorAll(".folder-toggle").forEach(toggle => {
|
||||||
toggle.addEventListener("click", function(e) {
|
toggle.addEventListener("click", function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const siblingUl = this.parentNode.querySelector("ul");
|
const siblingUl = this.parentNode.querySelector("ul");
|
||||||
if (siblingUl) {
|
if (siblingUl) {
|
||||||
if (siblingUl.style.display === "none" || siblingUl.style.display === "") {
|
if (siblingUl.classList.contains("collapsed") || !siblingUl.classList.contains("expanded")) {
|
||||||
siblingUl.style.display = "block";
|
siblingUl.classList.remove("collapsed");
|
||||||
|
siblingUl.classList.add("expanded");
|
||||||
this.textContent = "[-]";
|
this.textContent = "[-]";
|
||||||
} else {
|
} else {
|
||||||
siblingUl.style.display = "none";
|
siblingUl.classList.remove("expanded");
|
||||||
|
siblingUl.classList.add("collapsed");
|
||||||
this.textContent = "[+]";
|
this.textContent = "[+]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +212,6 @@ export function loadFolderList(selectedFolder) {
|
|||||||
loadFolderTree(selectedFolder);
|
loadFolderTree(selectedFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ----------------------
|
// ----------------------
|
||||||
// Folder Management Functions
|
// Folder Management Functions
|
||||||
// ----------------------
|
// ----------------------
|
||||||
@@ -209,19 +226,15 @@ function openRenameFolderModal() {
|
|||||||
showToast("Please select a valid folder to rename.");
|
showToast("Please select a valid folder to rename.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Pre-fill the input with the current folder name (optional)
|
|
||||||
document.getElementById("newRenameFolderName").value = selectedFolder;
|
document.getElementById("newRenameFolderName").value = selectedFolder;
|
||||||
// Show the modal
|
|
||||||
document.getElementById("renameFolderModal").style.display = "block";
|
document.getElementById("renameFolderModal").style.display = "block";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach event listener for Cancel button in the rename modal
|
|
||||||
document.getElementById("cancelRenameFolder").addEventListener("click", function () {
|
document.getElementById("cancelRenameFolder").addEventListener("click", function () {
|
||||||
document.getElementById("renameFolderModal").style.display = "none";
|
document.getElementById("renameFolderModal").style.display = "none";
|
||||||
document.getElementById("newRenameFolderName").value = "";
|
document.getElementById("newRenameFolderName").value = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Attach event listener for the Rename (Submit) button in the rename modal
|
|
||||||
document.getElementById("submitRenameFolder").addEventListener("click", function () {
|
document.getElementById("submitRenameFolder").addEventListener("click", function () {
|
||||||
const selectedFolder = window.currentFolder || "root";
|
const selectedFolder = window.currentFolder || "root";
|
||||||
const newFolderName = document.getElementById("newRenameFolderName").value.trim();
|
const newFolderName = document.getElementById("newRenameFolderName").value.trim();
|
||||||
@@ -247,7 +260,6 @@ document.getElementById("submitRenameFolder").addEventListener("click", function
|
|||||||
})
|
})
|
||||||
.catch(error => console.error("Error renaming folder:", error))
|
.catch(error => console.error("Error renaming folder:", error))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
// Hide the modal and clear the input
|
|
||||||
document.getElementById("renameFolderModal").style.display = "none";
|
document.getElementById("renameFolderModal").style.display = "none";
|
||||||
document.getElementById("newRenameFolderName").value = "";
|
document.getElementById("newRenameFolderName").value = "";
|
||||||
});
|
});
|
||||||
@@ -259,19 +271,15 @@ function openDeleteFolderModal() {
|
|||||||
showToast("Please select a valid folder to delete.");
|
showToast("Please select a valid folder to delete.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Update the modal message to include the folder name.
|
|
||||||
document.getElementById("deleteFolderMessage").textContent =
|
document.getElementById("deleteFolderMessage").textContent =
|
||||||
"Are you sure you want to delete folder " + selectedFolder + "?";
|
"Are you sure you want to delete folder " + selectedFolder + "?";
|
||||||
// Show the modal.
|
|
||||||
document.getElementById("deleteFolderModal").style.display = "block";
|
document.getElementById("deleteFolderModal").style.display = "block";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach event for Cancel button in the delete modal.
|
|
||||||
document.getElementById("cancelDeleteFolder").addEventListener("click", function () {
|
document.getElementById("cancelDeleteFolder").addEventListener("click", function () {
|
||||||
document.getElementById("deleteFolderModal").style.display = "none";
|
document.getElementById("deleteFolderModal").style.display = "none";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Attach event for Confirm/Delete button.
|
|
||||||
document.getElementById("confirmDeleteFolder").addEventListener("click", function () {
|
document.getElementById("confirmDeleteFolder").addEventListener("click", function () {
|
||||||
const selectedFolder = window.currentFolder || "root";
|
const selectedFolder = window.currentFolder || "root";
|
||||||
fetch("deleteFolder.php", {
|
fetch("deleteFolder.php", {
|
||||||
@@ -293,23 +301,19 @@ document.getElementById("confirmDeleteFolder").addEventListener("click", functio
|
|||||||
})
|
})
|
||||||
.catch(error => console.error("Error deleting folder:", error))
|
.catch(error => console.error("Error deleting folder:", error))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
// Hide the modal after the request completes.
|
|
||||||
document.getElementById("deleteFolderModal").style.display = "none";
|
document.getElementById("deleteFolderModal").style.display = "none";
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Instead of using prompt, show the modal.
|
|
||||||
document.getElementById("createFolderBtn").addEventListener("click", function () {
|
document.getElementById("createFolderBtn").addEventListener("click", function () {
|
||||||
document.getElementById("createFolderModal").style.display = "block";
|
document.getElementById("createFolderModal").style.display = "block";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Attach event for the Cancel button.
|
|
||||||
document.getElementById("cancelCreateFolder").addEventListener("click", function () {
|
document.getElementById("cancelCreateFolder").addEventListener("click", function () {
|
||||||
document.getElementById("createFolderModal").style.display = "none";
|
document.getElementById("createFolderModal").style.display = "none";
|
||||||
document.getElementById("newFolderName").value = "";
|
document.getElementById("newFolderName").value = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Attach event for the Submit (Create) button.
|
|
||||||
document.getElementById("submitCreateFolder").addEventListener("click", function () {
|
document.getElementById("submitCreateFolder").addEventListener("click", function () {
|
||||||
const folderInput = document.getElementById("newFolderName").value.trim();
|
const folderInput = document.getElementById("newFolderName").value.trim();
|
||||||
if (!folderInput) {
|
if (!folderInput) {
|
||||||
@@ -337,7 +341,6 @@ document.getElementById("submitCreateFolder").addEventListener("click", function
|
|||||||
} else {
|
} else {
|
||||||
showToast("Error: " + (data.error || "Could not create folder"));
|
showToast("Error: " + (data.error || "Could not create folder"));
|
||||||
}
|
}
|
||||||
// Hide modal and clear input.
|
|
||||||
document.getElementById("createFolderModal").style.display = "none";
|
document.getElementById("createFolderModal").style.display = "none";
|
||||||
document.getElementById("newFolderName").value = "";
|
document.getElementById("newFolderName").value = "";
|
||||||
})
|
})
|
||||||
|
|||||||
152
index.html
152
index.html
@@ -2,46 +2,53 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Multi File Upload Editor</title>
|
<title>Multi File Upload Editor</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg">
|
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg" />
|
||||||
<!-- External CSS -->
|
<!-- External CSS -->
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css" />
|
||||||
<!-- Google Fonts and Material Icons -->
|
<!-- Google Fonts and Material Icons -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet" />
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
<link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css">
|
<link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" />
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/theme/material-darker.min.css">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/xml/xml.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/xml/xml.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/css/css.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/css/css.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/javascript/javascript.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/javascript/javascript.min.js"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!-- Header -->
|
<header class="header-container">
|
||||||
<header>
|
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<img src="/assets/logo.svg" alt="Filing Cabinet Logo">
|
<img src="/assets/logo.svg" alt="Filing Cabinet Logo" class="header-logo">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-title">
|
<div class="header-title">
|
||||||
<h1>Multi File Upload Editor</h1>
|
<h1>Multi File Upload Editor</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-buttons">
|
|
||||||
<button id="logoutBtn" title="Logout">
|
<div class="header-right">
|
||||||
<i class="material-icons">exit_to_app</i>
|
<div class="header-buttons">
|
||||||
</button>
|
<button id="logoutBtn" title="Logout">
|
||||||
<button id="addUserBtn" title="Add User">
|
<i class="material-icons">exit_to_app</i>
|
||||||
<i class="material-icons">person_add</i>
|
</button>
|
||||||
</button>
|
<button id="addUserBtn" title="Add User">
|
||||||
<button id="removeUserBtn" title="Remove User">
|
<i class="material-icons">person_add</i>
|
||||||
<i class="material-icons">person_remove</i>
|
</button>
|
||||||
</button>
|
<button id="removeUserBtn" title="Remove User">
|
||||||
|
<i class="material-icons">person_remove</i>
|
||||||
|
</button>
|
||||||
|
<button id="darkModeToggle" class="dark-mode-toggle">Dark Mode</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Custom Toast Container -->
|
<!-- Custom Toast Container -->
|
||||||
<div id="customToast"></div>
|
<div id="customToast"></div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -51,11 +58,11 @@
|
|||||||
<form id="authForm" method="post">
|
<form id="authForm" method="post">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="loginUsername">User:</label>
|
<label for="loginUsername">User:</label>
|
||||||
<input type="text" class="form-control" id="loginUsername" name="username" required>
|
<input type="text" class="form-control" id="loginUsername" name="username" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="loginPassword">Password:</label>
|
<label for="loginPassword">Password:</label>
|
||||||
<input type="password" class="form-control" id="loginPassword" name="password" required>
|
<input type="password" class="form-control" id="loginPassword" name="password" required />
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-block">Login</button>
|
<button type="submit" class="btn btn-primary btn-block">Login</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -69,19 +76,14 @@
|
|||||||
<div class="col-md-6 col-lg-7 d-flex">
|
<div class="col-md-6 col-lg-7 d-flex">
|
||||||
<div class="card flex-fill">
|
<div class="card flex-fill">
|
||||||
<div class="card-header">Upload Files</div>
|
<div class="card-header">Upload Files</div>
|
||||||
<!-- Make card-body a flex container in column direction -->
|
<!-- Card body is a flex container in column direction -->
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body d-flex flex-column">
|
||||||
<form id="uploadFileForm" method="post" enctype="multipart/form-data" class="d-flex flex-column"
|
<form id="uploadFileForm" method="post" enctype="multipart/form-data" class="d-flex flex-column" style="height: 100%;">
|
||||||
style="height: 100%;">
|
|
||||||
<!-- Make the form-group container flex-grow so it fills available space -->
|
|
||||||
<div class="form-group flex-grow-1" style="margin-bottom: 1rem;">
|
<div class="form-group flex-grow-1" style="margin-bottom: 1rem;">
|
||||||
<!-- Set drop area to grow to fill its parent -->
|
<div id="uploadDropArea" style="border:2px dashed #ccc; padding:20px; cursor:pointer; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;">
|
||||||
<div id="uploadDropArea"
|
|
||||||
style="border:2px dashed #ccc; padding:20px; cursor:pointer; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;">
|
|
||||||
<span>Drop files here or click 'Choose files'</span>
|
<span>Drop files here or click 'Choose files'</span>
|
||||||
<br>
|
<br />
|
||||||
<input type="file" id="file" name="file[]" class="form-control-file" multiple required
|
<input type="file" id="file" name="file[]" class="form-control-file" multiple required style="display:none;" />
|
||||||
style="display:none;">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" id="uploadBtn" class="btn btn-primary d-block mx-auto">Upload</button>
|
<button type="submit" id="uploadBtn" class="btn btn-primary d-block mx-auto">Upload</button>
|
||||||
@@ -98,15 +100,14 @@
|
|||||||
<div class="form-group d-flex align-items-top" style="padding-top:0; margin-bottom:0;">
|
<div class="form-group d-flex align-items-top" style="padding-top:0; margin-bottom:0;">
|
||||||
<div id="folderTreeContainer"></div>
|
<div id="folderTreeContainer"></div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Wrap folder buttons in a container to prevent wrapping -->
|
<!-- Folder actions -->
|
||||||
<div class="folder-actions mt-3">
|
<div class="folder-actions mt-3">
|
||||||
<button id="createFolderBtn" class="btn btn-primary">Create Folder</button>
|
<button id="createFolderBtn" class="btn btn-primary">Create Folder</button>
|
||||||
<!-- Create Folder Modal -->
|
<!-- Create Folder Modal -->
|
||||||
<div id="createFolderModal" class="modal">
|
<div id="createFolderModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Create Folder</h4>
|
<h4>Create Folder</h4>
|
||||||
<input type="text" id="newFolderName" class="form-control" placeholder="Enter folder name"
|
<input type="text" id="newFolderName" class="form-control" placeholder="Enter folder name" style="margin-top:10px;" />
|
||||||
style="margin-top:10px;">
|
|
||||||
<div style="margin-top:15px; text-align:right;">
|
<div style="margin-top:15px; text-align:right;">
|
||||||
<button id="cancelCreateFolder" class="btn btn-secondary">Cancel</button>
|
<button id="cancelCreateFolder" class="btn btn-secondary">Cancel</button>
|
||||||
<button id="submitCreateFolder" class="btn btn-primary">Create</button>
|
<button id="submitCreateFolder" class="btn btn-primary">Create</button>
|
||||||
@@ -120,8 +121,7 @@
|
|||||||
<div id="renameFolderModal" class="modal">
|
<div id="renameFolderModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Rename Folder</h4>
|
<h4>Rename Folder</h4>
|
||||||
<input type="text" id="newRenameFolderName" class="form-control" placeholder="Enter new folder name"
|
<input type="text" id="newRenameFolderName" class="form-control" placeholder="Enter new folder name" style="margin-top:10px;" />
|
||||||
style="margin-top:10px;">
|
|
||||||
<div style="margin-top:15px; text-align:right;">
|
<div style="margin-top:15px; text-align:right;">
|
||||||
<button id="cancelRenameFolder" class="btn btn-secondary">Cancel</button>
|
<button id="cancelRenameFolder" class="btn btn-secondary">Cancel</button>
|
||||||
<button id="submitRenameFolder" class="btn btn-primary">Rename</button>
|
<button id="submitRenameFolder" class="btn btn-primary">Rename</button>
|
||||||
@@ -143,26 +143,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="folderExplanation" style="
|
<div id="folderExplanation" class="folder-explanation">
|
||||||
margin-top:15px;
|
<details class="folder-help-details">
|
||||||
font-size:12px;
|
<summary class="folder-help-summary">
|
||||||
color:#555;
|
<i class="material-icons folder-help-icon">info</i>
|
||||||
background-color:#f9f9f9;
|
|
||||||
border:1px solid #ddd;
|
|
||||||
border-radius:4px;
|
|
||||||
padding:10px;
|
|
||||||
">
|
|
||||||
<details style="margin-top:2px; font-size:12px; color:#555;">
|
|
||||||
<summary style="cursor:pointer; list-style: none; color: #000; background: #f9f9f9; padding: 2px;">
|
|
||||||
<i class="material-icons" style="vertical-align: middle; color: #d96601;">info</i>
|
|
||||||
Folder Navigation & Management Help Info
|
Folder Navigation & Management Help Info
|
||||||
</summary>
|
</summary>
|
||||||
<ul style="margin: 0; padding-left:20px;">
|
<ul class="folder-help-list">
|
||||||
<li>To view files in a folder, click on the folder name in the tree.</li>
|
<li>Click on a folder in the tree to view its files.</li>
|
||||||
<li>[-] expands and [+] collapses a folder in the tree.</li>
|
<li>Use [-] to collapse and [+] to expand folders.</li>
|
||||||
<li>To create a subfolder, select a folder from the tree above and click "Create Folder".</li>
|
<li>Select a folder and click "Create Folder" to add a subfolder.</li>
|
||||||
<li>To rename or delete a folder, first select it from the tree, then click "Rename Folder" or
|
<li>To rename or delete a folder, select it and then click the appropriate button.</li>
|
||||||
"Delete Folder" respectively.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,16 +167,12 @@
|
|||||||
<div id="fileListContainer" style="display: none;">
|
<div id="fileListContainer" style="display: none;">
|
||||||
<h2 id="fileListTitle">Files in (Root)</h2>
|
<h2 id="fileListTitle">Files in (Root)</h2>
|
||||||
<div id="fileListActions" class="file-list-actions">
|
<div id="fileListActions" class="file-list-actions">
|
||||||
<button id="deleteSelectedBtn" class="btn action-btn" style="display: none;">
|
<button id="deleteSelectedBtn" class="btn action-btn" style="display: none;">Delete Files</button>
|
||||||
Delete Files
|
|
||||||
</button>
|
|
||||||
<!-- Delete Files Modal -->
|
<!-- Delete Files Modal -->
|
||||||
<div id="deleteFilesModal" class="modal">
|
<div id="deleteFilesModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Delete Selected Files</h4>
|
<h4>Delete Selected Files</h4>
|
||||||
<p id="deleteFilesMessage">
|
<p id="deleteFilesMessage">Are you sure you want to delete the selected files?</p>
|
||||||
Are you sure you want to delete the selected files?
|
|
||||||
</p>
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button id="cancelDeleteFiles" class="btn btn-secondary">Cancel</button>
|
<button id="cancelDeleteFiles" class="btn btn-secondary">Cancel</button>
|
||||||
<button id="confirmDeleteFiles" class="btn btn-danger">Delete</button>
|
<button id="confirmDeleteFiles" class="btn btn-danger">Delete</button>
|
||||||
@@ -193,16 +180,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="copySelectedBtn" class="btn action-btn" style="display: none;" disabled>
|
<button id="copySelectedBtn" class="btn action-btn" style="display: none;" disabled>Copy Files</button>
|
||||||
Copy Files
|
|
||||||
</button>
|
|
||||||
<!-- Copy Files Modal -->
|
<!-- Copy Files Modal -->
|
||||||
<div id="copyFilesModal" class="modal">
|
<div id="copyFilesModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Copy Selected Files</h4>
|
<h4>Copy Selected Files</h4>
|
||||||
<p id="copyFilesMessage">
|
<p id="copyFilesMessage">Select a target folder for copying the selected files:</p>
|
||||||
Select a target folder for copying the selected files:
|
|
||||||
</p>
|
|
||||||
<select id="copyTargetFolder" class="form-control modal-input"></select>
|
<select id="copyTargetFolder" class="form-control modal-input"></select>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button id="cancelCopyFiles" class="btn btn-secondary">Cancel</button>
|
<button id="cancelCopyFiles" class="btn btn-secondary">Cancel</button>
|
||||||
@@ -211,16 +194,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="moveSelectedBtn" class="btn action-btn" style="display: none;" disabled>
|
<button id="moveSelectedBtn" class="btn action-btn" style="display: none;" disabled>Move Files</button>
|
||||||
Move Files
|
|
||||||
</button>
|
|
||||||
<!-- Move Files Modal -->
|
<!-- Move Files Modal -->
|
||||||
<div id="moveFilesModal" class="modal">
|
<div id="moveFilesModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Move Selected Files</h4>
|
<h4>Move Selected Files</h4>
|
||||||
<p id="moveFilesMessage">
|
<p id="moveFilesMessage">Select a target folder for moving the selected files:</p>
|
||||||
Select a target folder for moving the selected files:
|
|
||||||
</p>
|
|
||||||
<select id="moveTargetFolder" class="form-control modal-input"></select>
|
<select id="moveTargetFolder" class="form-control modal-input"></select>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button id="cancelMoveFiles" class="btn btn-secondary">Cancel</button>
|
<button id="cancelMoveFiles" class="btn btn-secondary">Cancel</button>
|
||||||
@@ -228,9 +207,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="downloadZipBtn" class="btn action-btn" style="display: none;" disabled>
|
|
||||||
Download ZIP
|
<button id="downloadZipBtn" class="btn action-btn" style="display: none;" disabled>Download ZIP</button>
|
||||||
</button>
|
|
||||||
<!-- Download Zip Modal -->
|
<!-- Download Zip Modal -->
|
||||||
<div id="downloadZipModal" class="modal" style="display:none;">
|
<div id="downloadZipModal" class="modal" style="display:none;">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -253,11 +231,11 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h3>Create New User</h3>
|
<h3>Create New User</h3>
|
||||||
<label for="newUsername">Username:</label>
|
<label for="newUsername">Username:</label>
|
||||||
<input type="text" id="newUsername" class="form-control">
|
<input type="text" id="newUsername" class="form-control" />
|
||||||
<label for="newPassword">Password:</label>
|
<label for="newPassword">Password:</label>
|
||||||
<input type="password" id="newPassword" class="form-control">
|
<input type="password" id="newPassword" class="form-control" />
|
||||||
<div id="adminCheckboxContainer">
|
<div id="adminCheckboxContainer">
|
||||||
<input type="checkbox" id="isAdmin">
|
<input type="checkbox" id="isAdmin" />
|
||||||
<label for="isAdmin">Grant Admin Access</label>
|
<label for="isAdmin">Grant Admin Access</label>
|
||||||
</div>
|
</div>
|
||||||
<button id="saveUserBtn" class="btn btn-primary">Save User</button>
|
<button id="saveUserBtn" class="btn btn-primary">Save User</button>
|
||||||
@@ -280,8 +258,7 @@
|
|||||||
<div id="renameFileModal" class="modal">
|
<div id="renameFileModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4>Rename File</h4>
|
<h4>Rename File</h4>
|
||||||
<input type="text" id="newFileName" class="form-control" placeholder="Enter new file name"
|
<input type="text" id="newFileName" class="form-control" placeholder="Enter new file name" style="margin-top:10px;" />
|
||||||
style="margin-top:10px;">
|
|
||||||
<div style="margin-top:15px; text-align:right;">
|
<div style="margin-top:15px; text-align:right;">
|
||||||
<button id="cancelRenameFile" class="btn btn-secondary">Cancel</button>
|
<button id="cancelRenameFile" class="btn btn-secondary">Cancel</button>
|
||||||
<button id="submitRenameFile" class="btn btn-primary">Rename</button>
|
<button id="submitRenameFile" class="btn btn-primary">Rename</button>
|
||||||
@@ -292,6 +269,13 @@
|
|||||||
<!-- JavaScript Files -->
|
<!-- JavaScript Files -->
|
||||||
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||||
<script type="module" src="main.js"></script>
|
<script type="module" src="main.js"></script>
|
||||||
|
<script>
|
||||||
|
// Dark mode toggle script
|
||||||
|
const darkModeToggle = document.getElementById('darkModeToggle');
|
||||||
|
darkModeToggle.addEventListener('click', () => {
|
||||||
|
document.body.classList.toggle('dark-mode');
|
||||||
|
darkModeToggle.innerText = document.body.classList.contains('dark-mode') ? 'Light Mode' : 'Dark Mode';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
57
main.js
57
main.js
@@ -34,23 +34,46 @@ window.currentFolder = "root";
|
|||||||
// DOMContentLoaded initialization.
|
// DOMContentLoaded initialization.
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Call initAuth synchronously.
|
// Call initAuth synchronously.
|
||||||
initAuth();
|
initAuth();
|
||||||
const message = sessionStorage.getItem("welcomeMessage");
|
|
||||||
if (message) {
|
// Check OS theme preference & apply dark mode
|
||||||
showToast(message);
|
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
sessionStorage.removeItem("welcomeMessage");
|
document.body.classList.add("dark-mode"); // Enable dark mode if OS is set to dark
|
||||||
}
|
}
|
||||||
checkAuthentication().then(authenticated => {
|
|
||||||
if (authenticated) {
|
// Listen for real-time OS theme changes
|
||||||
window.currentFolder = "root";
|
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (event) => {
|
||||||
loadFileList(window.currentFolder);
|
if (event.matches) {
|
||||||
initFileActions();
|
document.body.classList.add("dark-mode"); // Enable dark mode
|
||||||
initUpload();
|
|
||||||
loadFolderTree();
|
|
||||||
} else {
|
} else {
|
||||||
console.warn("User not authenticated. Data loading deferred.");
|
document.body.classList.remove("dark-mode"); // Disable dark mode
|
||||||
// Optionally redirect to login
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ✅ Fix the Button Label on Page Load
|
||||||
|
const darkModeToggle = document.getElementById("darkModeToggle");
|
||||||
|
if (document.body.classList.contains("dark-mode")) {
|
||||||
|
darkModeToggle.textContent = "Light Mode";
|
||||||
|
} else {
|
||||||
|
darkModeToggle.textContent = "Dark Mode";
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = sessionStorage.getItem("welcomeMessage");
|
||||||
|
if (message) {
|
||||||
|
showToast(message);
|
||||||
|
sessionStorage.removeItem("welcomeMessage");
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAuthentication().then(authenticated => {
|
||||||
|
if (authenticated) {
|
||||||
|
window.currentFolder = "root";
|
||||||
|
loadFileList(window.currentFolder);
|
||||||
|
initFileActions();
|
||||||
|
initUpload();
|
||||||
|
loadFolderTree();
|
||||||
|
} else {
|
||||||
|
console.warn("User not authenticated. Data loading deferred.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
882
styles.css
882
styles.css
File diff suppressed because it is too large
Load Diff
67
upload.js
67
upload.js
@@ -9,43 +9,33 @@ export function initUpload() {
|
|||||||
const uploadForm = document.getElementById("uploadFileForm");
|
const uploadForm = document.getElementById("uploadFileForm");
|
||||||
const dropArea = document.getElementById("uploadDropArea");
|
const dropArea = document.getElementById("uploadDropArea");
|
||||||
|
|
||||||
// Helper function: set the drop area's default layout.
|
// Helper function: set the drop area's default layout using CSS classes.
|
||||||
function setDropAreaDefault() {
|
function setDropAreaDefault() {
|
||||||
if (dropArea) {
|
if (dropArea) {
|
||||||
dropArea.innerHTML = `
|
dropArea.innerHTML = `
|
||||||
<div id="uploadInstruction" style="margin-bottom:10px; font-size:16px;">
|
<div id="uploadInstruction" class="upload-instruction">
|
||||||
Drop files here or click 'Choose files'
|
Drop files here or click 'Choose files'
|
||||||
</div>
|
</div>
|
||||||
<div id="uploadFileRow" style="display:flex; align-items:center; justify-content:center;">
|
<div id="uploadFileRow" class="upload-file-row">
|
||||||
<button id="customChooseBtn" type="button">
|
<button id="customChooseBtn" type="button">
|
||||||
Choose files
|
Choose files
|
||||||
</button>
|
</button>
|
||||||
<div id="fileInfoContainer" style="margin-left:10px; font-size:16px; display:flex; align-items:center;">
|
<div id="fileInfoContainer" class="file-info-container">
|
||||||
<span id="fileInfoDefault">No files selected</span>
|
<span id="fileInfoDefault">No files selected</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
// Wire up the custom button.
|
// (Optional: wire up the custom button if needed.)
|
||||||
/* const customChooseBtn = document.getElementById("customChooseBtn");
|
|
||||||
if (customChooseBtn) {
|
|
||||||
customChooseBtn.addEventListener("click", function () {
|
|
||||||
fileInput.click();
|
|
||||||
});
|
|
||||||
} */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize drop area.
|
// Initialize drop area.
|
||||||
if (dropArea) {
|
if (dropArea) {
|
||||||
dropArea.style.border = "2px dashed #ccc";
|
// Instead of inline styles here, ensure dropArea is styled in CSS.
|
||||||
dropArea.style.padding = "20px";
|
// But if necessary, you can add minimal inline styles that you later override:
|
||||||
dropArea.style.textAlign = "center";
|
dropArea.classList.add("upload-drop-area"); // Define in CSS if needed.
|
||||||
dropArea.style.marginBottom = "15px";
|
|
||||||
dropArea.style.cursor = "pointer";
|
|
||||||
// Set default content once.
|
|
||||||
setDropAreaDefault();
|
setDropAreaDefault();
|
||||||
|
|
||||||
// Prevent default behavior for drag events.
|
|
||||||
dropArea.addEventListener("dragover", function (e) {
|
dropArea.addEventListener("dragover", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dropArea.style.backgroundColor = "#f8f8f8";
|
dropArea.style.backgroundColor = "#f8f8f8";
|
||||||
@@ -63,30 +53,27 @@ export function initUpload() {
|
|||||||
fileInput.dispatchEvent(new Event("change"));
|
fileInput.dispatchEvent(new Event("change"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clicking the drop area triggers file selection.
|
|
||||||
dropArea.addEventListener("click", function () {
|
dropArea.addEventListener("click", function () {
|
||||||
fileInput.click();
|
fileInput.click();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// When files are selected, update only the file info container.
|
// When files are selected, update file info container.
|
||||||
if (fileInput) {
|
if (fileInput) {
|
||||||
fileInput.addEventListener("change", function () {
|
fileInput.addEventListener("change", function () {
|
||||||
const files = fileInput.files;
|
const files = fileInput.files;
|
||||||
// Update the file info container without replacing the entire drop area.
|
|
||||||
const fileInfoContainer = document.getElementById("fileInfoContainer");
|
const fileInfoContainer = document.getElementById("fileInfoContainer");
|
||||||
if (fileInfoContainer) {
|
if (fileInfoContainer) {
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
fileInfoContainer.innerHTML = `
|
fileInfoContainer.innerHTML = `
|
||||||
<div id="filePreviewContainer" style="display:inline-block; vertical-align:middle;"></div>
|
<div id="filePreviewContainer" class="file-preview-container"></div>
|
||||||
<span id="fileNameDisplay" style="vertical-align:middle; margin-left:5px;">${escapeHTML(files[0].name)}</span>
|
<span id="fileNameDisplay" class="file-name-display">${escapeHTML(files[0].name)}</span>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
fileInfoContainer.innerHTML = `
|
fileInfoContainer.innerHTML = `
|
||||||
<div id="filePreviewContainer" style="display:inline-block; vertical-align:middle;"></div>
|
<div id="filePreviewContainer" class="file-preview-container"></div>
|
||||||
<span id="fileCountDisplay" style="vertical-align:middle; margin-left:5px;">${files.length} files selected</span>
|
<span id="fileCountDisplay" class="file-name-display">${files.length} files selected</span>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
const previewContainer = document.getElementById("filePreviewContainer");
|
const previewContainer = document.getElementById("filePreviewContainer");
|
||||||
@@ -99,35 +86,30 @@ export function initUpload() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build progress list as before.
|
// Build progress list using CSS classes.
|
||||||
progressContainer.innerHTML = "";
|
progressContainer.innerHTML = "";
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
const allFiles = Array.from(files);
|
const allFiles = Array.from(files);
|
||||||
const maxDisplay = 10;
|
const maxDisplay = 10;
|
||||||
const list = document.createElement("ul");
|
const list = document.createElement("ul");
|
||||||
list.style.listStyle = "none";
|
list.classList.add("upload-progress-list");
|
||||||
list.style.padding = "0";
|
|
||||||
allFiles.forEach((file, index) => {
|
allFiles.forEach((file, index) => {
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
li.style.paddingTop = "10px";
|
li.classList.add("upload-progress-item");
|
||||||
li.style.marginBottom = "10px";
|
// For dynamic display, we still set display property via JS.
|
||||||
li.style.display = (index < maxDisplay) ? "flex" : "none";
|
li.style.display = (index < maxDisplay) ? "flex" : "none";
|
||||||
li.style.alignItems = "center";
|
|
||||||
li.style.flexWrap = "wrap";
|
|
||||||
|
|
||||||
const preview = document.createElement("div");
|
const preview = document.createElement("div");
|
||||||
preview.className = "file-preview";
|
preview.className = "file-preview"; // Already styled in CSS.
|
||||||
displayFilePreview(file, preview);
|
displayFilePreview(file, preview);
|
||||||
|
|
||||||
const nameDiv = document.createElement("div");
|
const nameDiv = document.createElement("div");
|
||||||
// Using textContent here is safe, so no need to escape
|
nameDiv.classList.add("upload-file-name");
|
||||||
nameDiv.textContent = file.name;
|
nameDiv.textContent = file.name;
|
||||||
nameDiv.style.flexGrow = "1";
|
|
||||||
nameDiv.style.marginLeft = "5px";
|
|
||||||
nameDiv.style.wordBreak = "break-word";
|
|
||||||
|
|
||||||
const progDiv = document.createElement("div");
|
const progDiv = document.createElement("div");
|
||||||
progDiv.classList.add("progress");
|
progDiv.classList.add("progress", "upload-progress-div");
|
||||||
|
// If needed, dynamic style for flex sizing remains:
|
||||||
progDiv.style.flex = "0 0 250px";
|
progDiv.style.flex = "0 0 250px";
|
||||||
progDiv.style.marginLeft = "5px";
|
progDiv.style.marginLeft = "5px";
|
||||||
|
|
||||||
@@ -146,10 +128,9 @@ export function initUpload() {
|
|||||||
});
|
});
|
||||||
if (allFiles.length > maxDisplay) {
|
if (allFiles.length > maxDisplay) {
|
||||||
const extra = document.createElement("li");
|
const extra = document.createElement("li");
|
||||||
extra.style.paddingTop = "10px";
|
extra.classList.add("upload-progress-extra");
|
||||||
extra.style.marginBottom = "10px";
|
|
||||||
extra.textContent = `Uploading additional ${allFiles.length - maxDisplay} file(s)...`;
|
extra.textContent = `Uploading additional ${allFiles.length - maxDisplay} file(s)...`;
|
||||||
extra.style.display = "flex";
|
extra.style.display = "flex"; // If dynamic, otherwise define in CSS.
|
||||||
list.appendChild(extra);
|
list.appendChild(extra);
|
||||||
}
|
}
|
||||||
progressContainer.appendChild(list);
|
progressContainer.appendChild(list);
|
||||||
@@ -157,7 +138,7 @@ export function initUpload() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit handler remains unchanged.
|
// Submit handler.
|
||||||
if (uploadForm) {
|
if (uploadForm) {
|
||||||
uploadForm.addEventListener("submit", function (e) {
|
uploadForm.addEventListener("submit", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
Reference in New Issue
Block a user