css refactoring & auto dark/light mode

This commit is contained in:
Ryan
2025-03-11 03:44:57 -04:00
committed by GitHub
parent 60e58be8d9
commit 89d2fc2a41
7 changed files with 1057 additions and 370 deletions

View File

@@ -1,7 +1,11 @@
# Multi File Upload Editor # Multi File Upload Editor
**Light mode**
![Main Screen](https://raw.githubusercontent.com/error311/multi-file-upload-editor/refs/heads/master/resources/main-screen.png) ![Main Screen](https://raw.githubusercontent.com/error311/multi-file-upload-editor/refs/heads/master/resources/main-screen.png)
**Dark mode**
![Main Screen](https://raw.githubusercontent.com/error311/multi-file-upload-editor/refs/heads/master/resources/dark-mode.png)
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. Its 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. Its 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 PHPs 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 PHPs 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 systems 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.
--- ---

View File

@@ -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;">&times;</span> <span id="closeImageModal" class="close-image-modal">&times;</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,20 +602,26 @@ 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/"
: "uploads/" + folderUsed.split("/").map(encodeURIComponent).join("/") + "/"; : "uploads/" + folderUsed.split("/").map(encodeURIComponent).join("/") + "/";
const fileUrl = folderPath + encodeURIComponent(fileName) + "?t=" + new Date().getTime(); const fileUrl = folderPath + encodeURIComponent(fileName) + "?t=" + new Date().getTime();
fetch(fileUrl, { method: "HEAD" }) fetch(fileUrl, { method: "HEAD" })
.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);
} }
} }

View File

@@ -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,52 +97,62 @@ 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.");
const tree = buildFolderTree(folders); return;
}
// Build the root row.
let html = `<div id="rootRow" style="margin-bottom:10px; display:flex; align-items:center;">`; let html = "";
html += `<span class="folder-toggle" style="cursor:pointer; margin-right:5px;">[-]</span>`; // Build the root row without inline styles.
html += `<span class="folder-option" data-folder="root" style="cursor:pointer; font-weight:bold;">(Root)</span>`; html += `<div id="rootRow" class="root-row">
html += `</div>`; <span class="folder-toggle">[-]</span>
// Append the nested tree for root. Force its display to "block". <span class="folder-option root-folder-option" data-folder="root">(Root)</span>
html += renderFolderTree(tree, "", "block"); </div>`;
// If no folders exist (empty array), render a default tree with just (Root).
if (folders.length === 0) {
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;
} else if (!window.currentFolder) { } else if (!window.currentFolder) {
window.currentFolder = "root"; window.currentFolder = "root";
} }
document.getElementById("fileListTitle").textContent = document.getElementById("fileListTitle").textContent =
window.currentFolder === "root" ? "Files in (Root)" : "Files in (" + window.currentFolder + ")"; window.currentFolder === "root" ? "Files in (Root)" : "Files in (" + window.currentFolder + ")";
loadFileList(window.currentFolder); loadFileList(window.currentFolder);
if (window.currentFolder !== "root") { if (window.currentFolder !== "root") {
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");
@@ -151,34 +163,40 @@ export async function loadFolderTree(selectedFolder) {
loadFileList(selected); loadFileList(selected);
}); });
}); });
// 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 = "";
}) })

View File

@@ -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 &amp; Management Help Info Folder Navigation &amp; 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
View File

@@ -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.");
}
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -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();