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
|
||||
|
||||
**Light mode**
|
||||

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

|
||||
|
||||
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.
|
||||
@@ -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.
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
134
fileManager.js
134
fileManager.js
@@ -133,7 +133,7 @@ export function renderFileTable(folder) {
|
||||
const topControlsHTML = `
|
||||
<div class="row align-items-center mb-3">
|
||||
<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">
|
||||
<span class="input-group-text" id="searchIcon">
|
||||
<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="d-flex justify-content-center justify-content-md-start align-items-center">
|
||||
<button class="custom-prev-next-btn" ${currentPage === 1 ? "disabled" : ""} onclick="changePage(${currentPage - 1})">Prev</button>
|
||||
<span 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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -156,12 +156,12 @@ export function renderFileTable(folder) {
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40px;"><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="modified" class="hide-small" style="cursor:pointer; white-space: nowrap;">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="size" class="hide-small" style="cursor:pointer; white-space: nowrap;">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 class="checkbox-col"><input type="checkbox" id="selectAll" onclick="toggleAllCheckboxes(this)"></th>
|
||||
<th data-column="name" class="sortable-col">File Name ${sortOrder.column === "name" ? (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 sortable-col">Upload Date ${sortOrder.column === "uploaded" ? (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 sortable-col">Uploader ${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -185,21 +185,21 @@ export function renderFileTable(folder) {
|
||||
|
||||
// Build the preview button HTML string using the file's properties directly.
|
||||
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>
|
||||
</button>`
|
||||
: "";
|
||||
|
||||
: "";
|
||||
|
||||
tableBody += `
|
||||
<tr onclick="toggleRowSelection(event, '${safeFileName}')" style="cursor:pointer;">
|
||||
<tr onclick="toggleRowSelection(event, '${safeFileName}')" class="clickable-row">
|
||||
<td>
|
||||
<input type="checkbox" class="file-checkbox" value="${safeFileName}" onclick="event.stopPropagation(); updateRowHighlight(this);">
|
||||
</td>
|
||||
<td>${safeFileName}</td>
|
||||
<td class="hide-small" style="white-space: nowrap;">${safeModified}</td>
|
||||
<td class="hide-small hide-medium" style="white-space: nowrap;">${safeUploaded}</td>
|
||||
<td class="hide-small" style="white-space: nowrap;">${safeSize}</td>
|
||||
<td class="hide-small hide-medium" style="white-space: nowrap;">${safeUploader}</td>
|
||||
<td class="hide-small nowrap">${safeModified}</td>
|
||||
<td class="hide-small hide-medium nowrap">${safeUploaded}</td>
|
||||
<td class="hide-small nowrap">${safeSize}</td>
|
||||
<td class="hide-small hide-medium nowrap">${safeUploader}</td>
|
||||
<td>
|
||||
<div class="button-wrap">
|
||||
<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>`;
|
||||
|
||||
const bottomControlsHTML = `
|
||||
<div class="d-flex align-items-center mt-3" style="font-size:16px; line-height:1.5;">
|
||||
<label class="mr-2 mb-0" style="font-size:16px; line-height:1.5;">Show</label>
|
||||
<select class="form-control" style="width:auto; font-size:16px; height:auto;" onchange="changeItemsPerPage(this.value)">
|
||||
<div class="d-flex align-items-center mt-3 bottom-controls">
|
||||
<label class="label-inline mr-2 mb-0">Show</label>
|
||||
<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("")}
|
||||
</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>
|
||||
`;
|
||||
|
||||
@@ -274,10 +274,10 @@ window.previewImage = function (imageUrl, fileName) {
|
||||
zIndex: "1000"
|
||||
});
|
||||
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;">
|
||||
<span id="closeImageModal" style="position: absolute; top: 10px; right: 20px; font-size: 28px; cursor: pointer;">×</span>
|
||||
<h4 style="text-align: center; margin: 0 0 10px;"></h4>
|
||||
<img src="" style="max-width: 100%; max-height: 80vh; object-fit: contain; display: block; margin: 0 auto;" />
|
||||
<div class="modal-content image-preview-modal-content">
|
||||
<span id="closeImageModal" class="close-image-modal">×</span>
|
||||
<h4 class="image-modal-header"></h4>
|
||||
<img src="" class="image-modal-img" />
|
||||
</div>`;
|
||||
document.body.appendChild(modal);
|
||||
document.getElementById("closeImageModal").addEventListener("click", function () {
|
||||
@@ -602,20 +602,26 @@ function getModeForFile(fileName) {
|
||||
|
||||
export function editFile(fileName, folder) {
|
||||
console.log("Edit button clicked for:", fileName);
|
||||
|
||||
// Remove any existing editor modal before creating a new one
|
||||
let existingEditor = document.getElementById("editorContainer");
|
||||
if (existingEditor) { existingEditor.remove(); }
|
||||
if (existingEditor) {
|
||||
existingEditor.remove();
|
||||
}
|
||||
|
||||
const folderUsed = folder || window.currentFolder || "root";
|
||||
const folderPath = (folderUsed === "root")
|
||||
? "uploads/"
|
||||
: "uploads/" + folderUsed.split("/").map(encodeURIComponent).join("/") + "/";
|
||||
const fileUrl = folderPath + encodeURIComponent(fileName) + "?t=" + new Date().getTime();
|
||||
|
||||
|
||||
fetch(fileUrl, { method: "HEAD" })
|
||||
.then(response => {
|
||||
const contentLength = response.headers.get("Content-Length");
|
||||
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.");
|
||||
throw new Error("File too large.");
|
||||
}
|
||||
@@ -628,55 +634,76 @@ export function editFile(fileName, folder) {
|
||||
return response.text();
|
||||
})
|
||||
.then(content => {
|
||||
// Create the editor modal
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "editorContainer";
|
||||
modal.classList.add("modal", "editor-modal");
|
||||
modal.innerHTML = `
|
||||
<h3>Editing: ${fileName}</h3>
|
||||
<div id="editorControls" style="text-align:right; margin-bottom:5px;">
|
||||
<button id="decreaseFont" class="btn btn-sm btn-secondary">A-</button>
|
||||
<button id="increaseFont" class="btn btn-sm btn-secondary">A+</button>
|
||||
</div>
|
||||
<textarea id="fileEditor" style="width:100%; height:60%; resize:none;">${content}</textarea>
|
||||
<div style="margin-top:10px; text-align:right;">
|
||||
<button id="saveBtn" class="btn btn-primary">Save</button>
|
||||
<button id="closeBtn" class="btn btn-secondary">Close</button>
|
||||
</div>
|
||||
`;
|
||||
<h3 class="editor-title">Editing: ${fileName}</h3>
|
||||
<div id="editorControls" class="editor-controls">
|
||||
<button id="decreaseFont" class="btn btn-sm btn-secondary">A-</button>
|
||||
<button id="increaseFont" class="btn btn-sm btn-secondary">A+</button>
|
||||
</div>
|
||||
<textarea id="fileEditor" class="editor-textarea">${content}</textarea>
|
||||
<div class="editor-footer">
|
||||
<button id="saveBtn" class="btn btn-primary">Save</button>
|
||||
<button id="closeBtn" class="btn btn-secondary">Close</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
modal.style.display = "block";
|
||||
|
||||
|
||||
// Determine file mode and set CodeMirror editor
|
||||
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"), {
|
||||
lineNumbers: true,
|
||||
mode: mode,
|
||||
theme: "default",
|
||||
theme: theme,
|
||||
viewportMargin: Infinity
|
||||
});
|
||||
|
||||
editor.setSize("100%", "60vh");
|
||||
window.currentEditor = editor;
|
||||
|
||||
// Font size control
|
||||
let currentFontSize = 14;
|
||||
editor.getWrapperElement().style.fontSize = currentFontSize + "px";
|
||||
editor.refresh();
|
||||
|
||||
document.getElementById("decreaseFont").addEventListener("click", function() {
|
||||
|
||||
document.getElementById("decreaseFont").addEventListener("click", function () {
|
||||
currentFontSize = Math.max(8, currentFontSize - 2);
|
||||
editor.getWrapperElement().style.fontSize = currentFontSize + "px";
|
||||
editor.refresh();
|
||||
});
|
||||
|
||||
document.getElementById("increaseFont").addEventListener("click", function() {
|
||||
|
||||
document.getElementById("increaseFont").addEventListener("click", function () {
|
||||
currentFontSize = Math.min(32, currentFontSize + 2);
|
||||
editor.getWrapperElement().style.fontSize = currentFontSize + "px";
|
||||
editor.refresh();
|
||||
});
|
||||
|
||||
document.getElementById("saveBtn").addEventListener("click", function() {
|
||||
|
||||
// Save function
|
||||
document.getElementById("saveBtn").addEventListener("click", function () {
|
||||
saveFile(fileName, folderUsed);
|
||||
});
|
||||
document.getElementById("closeBtn").addEventListener("click", function() {
|
||||
|
||||
// Close function
|
||||
document.getElementById("closeBtn").addEventListener("click", function () {
|
||||
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));
|
||||
}
|
||||
@@ -714,19 +741,12 @@ export function displayFilePreview(file, container) {
|
||||
if (file.type.startsWith("image/")) {
|
||||
const img = document.createElement("img");
|
||||
img.src = URL.createObjectURL(file);
|
||||
img.style.maxWidth = "100px";
|
||||
img.style.maxHeight = "100px";
|
||||
img.style.marginRight = "5px";
|
||||
img.style.marginLeft = "0px";
|
||||
img.classList.add("file-preview-img");
|
||||
container.appendChild(img);
|
||||
} else {
|
||||
const iconSpan = document.createElement("span");
|
||||
iconSpan.classList.add("material-icons");
|
||||
iconSpan.style.color = "#333";
|
||||
iconSpan.classList.add("material-icons", "file-icon");
|
||||
iconSpan.textContent = "insert_drive_file";
|
||||
iconSpan.style.marginRight = "0px";
|
||||
iconSpan.style.marginLeft = "0px";
|
||||
iconSpan.style.fontSize = "32px";
|
||||
container.appendChild(iconSpan);
|
||||
}
|
||||
}
|
||||
|
||||
119
folderManager.js
119
folderManager.js
@@ -1,5 +1,6 @@
|
||||
import { loadFileList } from './fileManager.js';
|
||||
import { showToast } from './domUtils.js';
|
||||
|
||||
// ----------------------
|
||||
// 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 {string} parentPath - The path prefix.
|
||||
* @param {string} defaultDisplay - "block" (open) or "none" (collapsed)
|
||||
*/
|
||||
function renderFolderTree(tree, parentPath = "", defaultDisplay = "none") {
|
||||
let html = `<ul style="list-style-type:none; padding-left:20px; margin:0; display:${defaultDisplay};">`;
|
||||
function renderFolderTree(tree, parentPath = "", defaultDisplay = "block") {
|
||||
// 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) {
|
||||
const fullPath = parentPath ? parentPath + "/" + folder : folder;
|
||||
const hasChildren = Object.keys(tree[folder]).length > 0;
|
||||
html += `<li style="margin:4px 0; display:block;">`;
|
||||
html += `<li class="folder-item">`;
|
||||
if (hasChildren) {
|
||||
// For nested levels (below root) default to collapsed: toggle label "[+]"
|
||||
html += `<span class="folder-toggle" style="cursor:pointer; margin-right:5px;">[+]</span>`;
|
||||
html += `<span class="folder-toggle">[+]</span>`;
|
||||
} 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) {
|
||||
// Nested children always collapse by default.
|
||||
html += renderFolderTree(tree[folder], fullPath, "none");
|
||||
}
|
||||
html += `</li>`;
|
||||
@@ -76,8 +77,9 @@ function expandTreePath(path) {
|
||||
if (option) {
|
||||
const li = option.parentNode;
|
||||
const nestedUl = li.querySelector("ul");
|
||||
if (nestedUl && (nestedUl.style.display === "none" || nestedUl.style.display === "")) {
|
||||
nestedUl.style.display = "block";
|
||||
if (nestedUl && (nestedUl.classList.contains("collapsed") || !nestedUl.classList.contains("expanded"))) {
|
||||
nestedUl.classList.remove("collapsed");
|
||||
nestedUl.classList.add("expanded");
|
||||
const toggle = li.querySelector(".folder-toggle");
|
||||
if (toggle) {
|
||||
toggle.textContent = "[-]";
|
||||
@@ -95,52 +97,62 @@ export async function loadFolderTree(selectedFolder) {
|
||||
try {
|
||||
const response = await fetch('getFolderList.php');
|
||||
|
||||
// Check for Unauthorized status
|
||||
if (response.status === 401) {
|
||||
console.error("Unauthorized: Please log in to view folders.");
|
||||
// Optionally, redirect to the login page:
|
||||
// window.location.href = "/login.html";
|
||||
return;
|
||||
}
|
||||
|
||||
const folders = await response.json();
|
||||
console.log("Fetched folders:", folders);
|
||||
if (!Array.isArray(folders)) {
|
||||
console.error("Folder list response is not an array:", folders);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const container = document.getElementById("folderTreeContainer");
|
||||
if (!container) return;
|
||||
|
||||
const tree = buildFolderTree(folders);
|
||||
|
||||
// Build the root row.
|
||||
let html = `<div id="rootRow" style="margin-bottom:10px; display:flex; align-items:center;">`;
|
||||
html += `<span class="folder-toggle" style="cursor:pointer; margin-right:5px;">[-]</span>`;
|
||||
html += `<span class="folder-option" data-folder="root" style="cursor:pointer; font-weight:bold;">(Root)</span>`;
|
||||
html += `</div>`;
|
||||
// Append the nested tree for root. Force its display to "block".
|
||||
html += renderFolderTree(tree, "", "block");
|
||||
|
||||
if (!container) {
|
||||
console.error("Folder tree container not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
let html = "";
|
||||
// Build the root row without inline styles.
|
||||
html += `<div id="rootRow" class="root-row">
|
||||
<span class="folder-toggle">[-]</span>
|
||||
<span class="folder-option root-folder-option" data-folder="root">(Root)</span>
|
||||
</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;
|
||||
|
||||
console.log("Rendered folder tree HTML:", container.innerHTML);
|
||||
|
||||
if (selectedFolder) {
|
||||
window.currentFolder = selectedFolder;
|
||||
} else if (!window.currentFolder) {
|
||||
window.currentFolder = "root";
|
||||
}
|
||||
|
||||
|
||||
document.getElementById("fileListTitle").textContent =
|
||||
window.currentFolder === "root" ? "Files in (Root)" : "Files in (" + window.currentFolder + ")";
|
||||
loadFileList(window.currentFolder);
|
||||
|
||||
|
||||
if (window.currentFolder !== "root") {
|
||||
expandTreePath(window.currentFolder);
|
||||
}
|
||||
|
||||
// --- Attach events ---
|
||||
|
||||
// Attach events to folder options.
|
||||
container.querySelectorAll(".folder-option").forEach(el => {
|
||||
el.addEventListener("click", function(e) {
|
||||
el.addEventListener("click", function (e) {
|
||||
e.stopPropagation();
|
||||
container.querySelectorAll(".folder-option").forEach(item => item.classList.remove("selected"));
|
||||
this.classList.add("selected");
|
||||
@@ -151,34 +163,40 @@ export async function loadFolderTree(selectedFolder) {
|
||||
loadFileList(selected);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Attach toggle event for the root toggle.
|
||||
const rootToggle = container.querySelector("#rootRow .folder-toggle");
|
||||
if (rootToggle) {
|
||||
rootToggle.addEventListener("click", function(e) {
|
||||
rootToggle.addEventListener("click", function (e) {
|
||||
e.stopPropagation();
|
||||
const nestedUl = container.querySelector("#rootRow + ul");
|
||||
if (nestedUl) {
|
||||
if (nestedUl.style.display === "none" || nestedUl.style.display === "") {
|
||||
nestedUl.style.display = "block";
|
||||
if (nestedUl.classList.contains("collapsed") || !nestedUl.classList.contains("expanded")) {
|
||||
nestedUl.classList.remove("collapsed");
|
||||
nestedUl.classList.add("expanded");
|
||||
this.textContent = "[-]";
|
||||
} else {
|
||||
nestedUl.style.display = "none";
|
||||
nestedUl.classList.remove("expanded");
|
||||
nestedUl.classList.add("collapsed");
|
||||
this.textContent = "[+]";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Attach toggle events for all folder toggles.
|
||||
container.querySelectorAll(".folder-toggle").forEach(toggle => {
|
||||
toggle.addEventListener("click", function(e) {
|
||||
toggle.addEventListener("click", function (e) {
|
||||
e.stopPropagation();
|
||||
const siblingUl = this.parentNode.querySelector("ul");
|
||||
if (siblingUl) {
|
||||
if (siblingUl.style.display === "none" || siblingUl.style.display === "") {
|
||||
siblingUl.style.display = "block";
|
||||
if (siblingUl.classList.contains("collapsed") || !siblingUl.classList.contains("expanded")) {
|
||||
siblingUl.classList.remove("collapsed");
|
||||
siblingUl.classList.add("expanded");
|
||||
this.textContent = "[-]";
|
||||
} else {
|
||||
siblingUl.style.display = "none";
|
||||
siblingUl.classList.remove("expanded");
|
||||
siblingUl.classList.add("collapsed");
|
||||
this.textContent = "[+]";
|
||||
}
|
||||
}
|
||||
@@ -194,7 +212,6 @@ export function loadFolderList(selectedFolder) {
|
||||
loadFolderTree(selectedFolder);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------
|
||||
// Folder Management Functions
|
||||
// ----------------------
|
||||
@@ -209,19 +226,15 @@ function openRenameFolderModal() {
|
||||
showToast("Please select a valid folder to rename.");
|
||||
return;
|
||||
}
|
||||
// Pre-fill the input with the current folder name (optional)
|
||||
document.getElementById("newRenameFolderName").value = selectedFolder;
|
||||
// Show the modal
|
||||
document.getElementById("renameFolderModal").style.display = "block";
|
||||
}
|
||||
|
||||
// Attach event listener for Cancel button in the rename modal
|
||||
document.getElementById("cancelRenameFolder").addEventListener("click", function () {
|
||||
document.getElementById("renameFolderModal").style.display = "none";
|
||||
document.getElementById("newRenameFolderName").value = "";
|
||||
});
|
||||
|
||||
// Attach event listener for the Rename (Submit) button in the rename modal
|
||||
document.getElementById("submitRenameFolder").addEventListener("click", function () {
|
||||
const selectedFolder = window.currentFolder || "root";
|
||||
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))
|
||||
.finally(() => {
|
||||
// Hide the modal and clear the input
|
||||
document.getElementById("renameFolderModal").style.display = "none";
|
||||
document.getElementById("newRenameFolderName").value = "";
|
||||
});
|
||||
@@ -259,19 +271,15 @@ function openDeleteFolderModal() {
|
||||
showToast("Please select a valid folder to delete.");
|
||||
return;
|
||||
}
|
||||
// Update the modal message to include the folder name.
|
||||
document.getElementById("deleteFolderMessage").textContent =
|
||||
"Are you sure you want to delete folder " + selectedFolder + "?";
|
||||
// Show the modal.
|
||||
document.getElementById("deleteFolderModal").style.display = "block";
|
||||
}
|
||||
|
||||
// Attach event for Cancel button in the delete modal.
|
||||
document.getElementById("cancelDeleteFolder").addEventListener("click", function () {
|
||||
document.getElementById("deleteFolderModal").style.display = "none";
|
||||
});
|
||||
|
||||
// Attach event for Confirm/Delete button.
|
||||
document.getElementById("confirmDeleteFolder").addEventListener("click", function () {
|
||||
const selectedFolder = window.currentFolder || "root";
|
||||
fetch("deleteFolder.php", {
|
||||
@@ -293,23 +301,19 @@ document.getElementById("confirmDeleteFolder").addEventListener("click", functio
|
||||
})
|
||||
.catch(error => console.error("Error deleting folder:", error))
|
||||
.finally(() => {
|
||||
// Hide the modal after the request completes.
|
||||
document.getElementById("deleteFolderModal").style.display = "none";
|
||||
});
|
||||
});
|
||||
|
||||
// Instead of using prompt, show the modal.
|
||||
document.getElementById("createFolderBtn").addEventListener("click", function () {
|
||||
document.getElementById("createFolderModal").style.display = "block";
|
||||
});
|
||||
|
||||
// Attach event for the Cancel button.
|
||||
document.getElementById("cancelCreateFolder").addEventListener("click", function () {
|
||||
document.getElementById("createFolderModal").style.display = "none";
|
||||
document.getElementById("newFolderName").value = "";
|
||||
});
|
||||
|
||||
// Attach event for the Submit (Create) button.
|
||||
document.getElementById("submitCreateFolder").addEventListener("click", function () {
|
||||
const folderInput = document.getElementById("newFolderName").value.trim();
|
||||
if (!folderInput) {
|
||||
@@ -337,7 +341,6 @@ document.getElementById("submitCreateFolder").addEventListener("click", function
|
||||
} else {
|
||||
showToast("Error: " + (data.error || "Could not create folder"));
|
||||
}
|
||||
// Hide modal and clear input.
|
||||
document.getElementById("createFolderModal").style.display = "none";
|
||||
document.getElementById("newFolderName").value = "";
|
||||
})
|
||||
|
||||
152
index.html
152
index.html
@@ -2,46 +2,53 @@
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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 -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<!-- 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/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css">
|
||||
<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 rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" />
|
||||
<!-- Bootstrap CSS -->
|
||||
<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 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/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/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/javascript/javascript.min.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<header class="header-container">
|
||||
<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 class="header-title">
|
||||
<h1>Multi File Upload Editor</h1>
|
||||
</div>
|
||||
<div class="header-buttons">
|
||||
<button id="logoutBtn" title="Logout">
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
</button>
|
||||
<button id="addUserBtn" title="Add User">
|
||||
<i class="material-icons">person_add</i>
|
||||
</button>
|
||||
<button id="removeUserBtn" title="Remove User">
|
||||
<i class="material-icons">person_remove</i>
|
||||
</button>
|
||||
|
||||
<div class="header-right">
|
||||
<div class="header-buttons">
|
||||
<button id="logoutBtn" title="Logout">
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
</button>
|
||||
<button id="addUserBtn" title="Add User">
|
||||
<i class="material-icons">person_add</i>
|
||||
</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>
|
||||
</header>
|
||||
|
||||
<!-- Custom Toast Container -->
|
||||
<div id="customToast"></div>
|
||||
<div class="container">
|
||||
@@ -51,11 +58,11 @@
|
||||
<form id="authForm" method="post">
|
||||
<div class="form-group">
|
||||
<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 class="form-group">
|
||||
<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>
|
||||
<button type="submit" class="btn btn-primary btn-block">Login</button>
|
||||
</form>
|
||||
@@ -69,19 +76,14 @@
|
||||
<div class="col-md-6 col-lg-7 d-flex">
|
||||
<div class="card flex-fill">
|
||||
<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">
|
||||
<form id="uploadFileForm" method="post" enctype="multipart/form-data" class="d-flex flex-column"
|
||||
style="height: 100%;">
|
||||
<!-- Make the form-group container flex-grow so it fills available space -->
|
||||
<form id="uploadFileForm" method="post" enctype="multipart/form-data" class="d-flex flex-column" style="height: 100%;">
|
||||
<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>
|
||||
<br>
|
||||
<input type="file" id="file" name="file[]" class="form-control-file" multiple required
|
||||
style="display:none;">
|
||||
<br />
|
||||
<input type="file" id="file" name="file[]" class="form-control-file" multiple required style="display:none;" />
|
||||
</div>
|
||||
</div>
|
||||
<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 id="folderTreeContainer"></div>
|
||||
</div>
|
||||
<!-- Wrap folder buttons in a container to prevent wrapping -->
|
||||
<!-- Folder actions -->
|
||||
<div class="folder-actions mt-3">
|
||||
<button id="createFolderBtn" class="btn btn-primary">Create Folder</button>
|
||||
<!-- Create Folder Modal -->
|
||||
<div id="createFolderModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Create Folder</h4>
|
||||
<input type="text" id="newFolderName" class="form-control" placeholder="Enter folder name"
|
||||
style="margin-top:10px;">
|
||||
<input type="text" id="newFolderName" class="form-control" placeholder="Enter folder name" style="margin-top:10px;" />
|
||||
<div style="margin-top:15px; text-align:right;">
|
||||
<button id="cancelCreateFolder" class="btn btn-secondary">Cancel</button>
|
||||
<button id="submitCreateFolder" class="btn btn-primary">Create</button>
|
||||
@@ -120,8 +121,7 @@
|
||||
<div id="renameFolderModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Rename Folder</h4>
|
||||
<input type="text" id="newRenameFolderName" class="form-control" placeholder="Enter new folder name"
|
||||
style="margin-top:10px;">
|
||||
<input type="text" id="newRenameFolderName" class="form-control" placeholder="Enter new folder name" style="margin-top:10px;" />
|
||||
<div style="margin-top:15px; text-align:right;">
|
||||
<button id="cancelRenameFolder" class="btn btn-secondary">Cancel</button>
|
||||
<button id="submitRenameFolder" class="btn btn-primary">Rename</button>
|
||||
@@ -143,26 +143,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="folderExplanation" style="
|
||||
margin-top:15px;
|
||||
font-size:12px;
|
||||
color:#555;
|
||||
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>
|
||||
<div id="folderExplanation" class="folder-explanation">
|
||||
<details class="folder-help-details">
|
||||
<summary class="folder-help-summary">
|
||||
<i class="material-icons folder-help-icon">info</i>
|
||||
Folder Navigation & Management Help Info
|
||||
</summary>
|
||||
<ul style="margin: 0; padding-left:20px;">
|
||||
<li>To view files in a folder, click on the folder name in the tree.</li>
|
||||
<li>[-] expands and [+] collapses a folder in the tree.</li>
|
||||
<li>To create a subfolder, select a folder from the tree above and click "Create Folder".</li>
|
||||
<li>To rename or delete a folder, first select it from the tree, then click "Rename Folder" or
|
||||
"Delete Folder" respectively.</li>
|
||||
<ul class="folder-help-list">
|
||||
<li>Click on a folder in the tree to view its files.</li>
|
||||
<li>Use [-] to collapse and [+] to expand folders.</li>
|
||||
<li>Select a folder and click "Create Folder" to add a subfolder.</li>
|
||||
<li>To rename or delete a folder, select it and then click the appropriate button.</li>
|
||||
</ul>
|
||||
</details>
|
||||
</div>
|
||||
@@ -176,16 +167,12 @@
|
||||
<div id="fileListContainer" style="display: none;">
|
||||
<h2 id="fileListTitle">Files in (Root)</h2>
|
||||
<div id="fileListActions" class="file-list-actions">
|
||||
<button id="deleteSelectedBtn" class="btn action-btn" style="display: none;">
|
||||
Delete Files
|
||||
</button>
|
||||
<button id="deleteSelectedBtn" class="btn action-btn" style="display: none;">Delete Files</button>
|
||||
<!-- Delete Files Modal -->
|
||||
<div id="deleteFilesModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Delete Selected Files</h4>
|
||||
<p id="deleteFilesMessage">
|
||||
Are you sure you want to delete the selected files?
|
||||
</p>
|
||||
<p id="deleteFilesMessage">Are you sure you want to delete the selected files?</p>
|
||||
<div class="modal-footer">
|
||||
<button id="cancelDeleteFiles" class="btn btn-secondary">Cancel</button>
|
||||
<button id="confirmDeleteFiles" class="btn btn-danger">Delete</button>
|
||||
@@ -193,16 +180,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="copySelectedBtn" class="btn action-btn" style="display: none;" disabled>
|
||||
Copy Files
|
||||
</button>
|
||||
<button id="copySelectedBtn" class="btn action-btn" style="display: none;" disabled>Copy Files</button>
|
||||
<!-- Copy Files Modal -->
|
||||
<div id="copyFilesModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Copy Selected Files</h4>
|
||||
<p id="copyFilesMessage">
|
||||
Select a target folder for copying the selected files:
|
||||
</p>
|
||||
<p id="copyFilesMessage">Select a target folder for copying the selected files:</p>
|
||||
<select id="copyTargetFolder" class="form-control modal-input"></select>
|
||||
<div class="modal-footer">
|
||||
<button id="cancelCopyFiles" class="btn btn-secondary">Cancel</button>
|
||||
@@ -211,16 +194,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="moveSelectedBtn" class="btn action-btn" style="display: none;" disabled>
|
||||
Move Files
|
||||
</button>
|
||||
<button id="moveSelectedBtn" class="btn action-btn" style="display: none;" disabled>Move Files</button>
|
||||
<!-- Move Files Modal -->
|
||||
<div id="moveFilesModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Move Selected Files</h4>
|
||||
<p id="moveFilesMessage">
|
||||
Select a target folder for moving the selected files:
|
||||
</p>
|
||||
<p id="moveFilesMessage">Select a target folder for moving the selected files:</p>
|
||||
<select id="moveTargetFolder" class="form-control modal-input"></select>
|
||||
<div class="modal-footer">
|
||||
<button id="cancelMoveFiles" class="btn btn-secondary">Cancel</button>
|
||||
@@ -228,9 +207,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="downloadZipBtn" class="btn action-btn" style="display: none;" disabled>
|
||||
Download ZIP
|
||||
</button>
|
||||
|
||||
<button id="downloadZipBtn" class="btn action-btn" style="display: none;" disabled>Download ZIP</button>
|
||||
<!-- Download Zip Modal -->
|
||||
<div id="downloadZipModal" class="modal" style="display:none;">
|
||||
<div class="modal-content">
|
||||
@@ -253,11 +231,11 @@
|
||||
<div class="modal-content">
|
||||
<h3>Create New User</h3>
|
||||
<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>
|
||||
<input type="password" id="newPassword" class="form-control">
|
||||
<input type="password" id="newPassword" class="form-control" />
|
||||
<div id="adminCheckboxContainer">
|
||||
<input type="checkbox" id="isAdmin">
|
||||
<input type="checkbox" id="isAdmin" />
|
||||
<label for="isAdmin">Grant Admin Access</label>
|
||||
</div>
|
||||
<button id="saveUserBtn" class="btn btn-primary">Save User</button>
|
||||
@@ -280,8 +258,7 @@
|
||||
<div id="renameFileModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Rename File</h4>
|
||||
<input type="text" id="newFileName" class="form-control" placeholder="Enter new file name"
|
||||
style="margin-top:10px;">
|
||||
<input type="text" id="newFileName" class="form-control" placeholder="Enter new file name" style="margin-top:10px;" />
|
||||
<div style="margin-top:15px; text-align:right;">
|
||||
<button id="cancelRenameFile" class="btn btn-secondary">Cancel</button>
|
||||
<button id="submitRenameFile" class="btn btn-primary">Rename</button>
|
||||
@@ -292,6 +269,13 @@
|
||||
<!-- JavaScript Files -->
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.min.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>
|
||||
</html>
|
||||
57
main.js
57
main.js
@@ -34,23 +34,46 @@ window.currentFolder = "root";
|
||||
// DOMContentLoaded initialization.
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Call initAuth synchronously.
|
||||
initAuth();
|
||||
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();
|
||||
// Call initAuth synchronously.
|
||||
initAuth();
|
||||
|
||||
// Check OS theme preference & apply dark mode
|
||||
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
document.body.classList.add("dark-mode"); // Enable dark mode if OS is set to dark
|
||||
}
|
||||
|
||||
// Listen for real-time OS theme changes
|
||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (event) => {
|
||||
if (event.matches) {
|
||||
document.body.classList.add("dark-mode"); // Enable dark mode
|
||||
} else {
|
||||
console.warn("User not authenticated. Data loading deferred.");
|
||||
// Optionally redirect to login
|
||||
document.body.classList.remove("dark-mode"); // Disable dark mode
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ 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.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
886
styles.css
886
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 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() {
|
||||
if (dropArea) {
|
||||
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'
|
||||
</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">
|
||||
Choose files
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
// Wire up the custom button.
|
||||
/* const customChooseBtn = document.getElementById("customChooseBtn");
|
||||
if (customChooseBtn) {
|
||||
customChooseBtn.addEventListener("click", function () {
|
||||
fileInput.click();
|
||||
});
|
||||
} */
|
||||
// (Optional: wire up the custom button if needed.)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize drop area.
|
||||
if (dropArea) {
|
||||
dropArea.style.border = "2px dashed #ccc";
|
||||
dropArea.style.padding = "20px";
|
||||
dropArea.style.textAlign = "center";
|
||||
dropArea.style.marginBottom = "15px";
|
||||
dropArea.style.cursor = "pointer";
|
||||
// Set default content once.
|
||||
// Instead of inline styles here, ensure dropArea is styled in CSS.
|
||||
// But if necessary, you can add minimal inline styles that you later override:
|
||||
dropArea.classList.add("upload-drop-area"); // Define in CSS if needed.
|
||||
setDropAreaDefault();
|
||||
|
||||
// Prevent default behavior for drag events.
|
||||
dropArea.addEventListener("dragover", function (e) {
|
||||
e.preventDefault();
|
||||
dropArea.style.backgroundColor = "#f8f8f8";
|
||||
@@ -63,30 +53,27 @@ export function initUpload() {
|
||||
fileInput.dispatchEvent(new Event("change"));
|
||||
}
|
||||
});
|
||||
|
||||
// Clicking the drop area triggers file selection.
|
||||
dropArea.addEventListener("click", function () {
|
||||
fileInput.click();
|
||||
});
|
||||
}
|
||||
|
||||
// When files are selected, update only the file info container.
|
||||
// When files are selected, update file info container.
|
||||
if (fileInput) {
|
||||
fileInput.addEventListener("change", function () {
|
||||
const files = fileInput.files;
|
||||
// Update the file info container without replacing the entire drop area.
|
||||
const fileInfoContainer = document.getElementById("fileInfoContainer");
|
||||
if (fileInfoContainer) {
|
||||
if (files.length > 0) {
|
||||
if (files.length === 1) {
|
||||
fileInfoContainer.innerHTML = `
|
||||
<div id="filePreviewContainer" style="display:inline-block; vertical-align:middle;"></div>
|
||||
<span id="fileNameDisplay" style="vertical-align:middle; margin-left:5px;">${escapeHTML(files[0].name)}</span>
|
||||
<div id="filePreviewContainer" class="file-preview-container"></div>
|
||||
<span id="fileNameDisplay" class="file-name-display">${escapeHTML(files[0].name)}</span>
|
||||
`;
|
||||
} else {
|
||||
fileInfoContainer.innerHTML = `
|
||||
<div id="filePreviewContainer" style="display:inline-block; vertical-align:middle;"></div>
|
||||
<span id="fileCountDisplay" style="vertical-align:middle; margin-left:5px;">${files.length} files selected</span>
|
||||
<div id="filePreviewContainer" class="file-preview-container"></div>
|
||||
<span id="fileCountDisplay" class="file-name-display">${files.length} files selected</span>
|
||||
`;
|
||||
}
|
||||
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 = "";
|
||||
if (files.length > 0) {
|
||||
const allFiles = Array.from(files);
|
||||
const maxDisplay = 10;
|
||||
const list = document.createElement("ul");
|
||||
list.style.listStyle = "none";
|
||||
list.style.padding = "0";
|
||||
list.classList.add("upload-progress-list");
|
||||
allFiles.forEach((file, index) => {
|
||||
const li = document.createElement("li");
|
||||
li.style.paddingTop = "10px";
|
||||
li.style.marginBottom = "10px";
|
||||
li.classList.add("upload-progress-item");
|
||||
// For dynamic display, we still set display property via JS.
|
||||
li.style.display = (index < maxDisplay) ? "flex" : "none";
|
||||
li.style.alignItems = "center";
|
||||
li.style.flexWrap = "wrap";
|
||||
|
||||
const preview = document.createElement("div");
|
||||
preview.className = "file-preview";
|
||||
preview.className = "file-preview"; // Already styled in CSS.
|
||||
displayFilePreview(file, preview);
|
||||
|
||||
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.style.flexGrow = "1";
|
||||
nameDiv.style.marginLeft = "5px";
|
||||
nameDiv.style.wordBreak = "break-word";
|
||||
|
||||
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.marginLeft = "5px";
|
||||
|
||||
@@ -146,10 +128,9 @@ export function initUpload() {
|
||||
});
|
||||
if (allFiles.length > maxDisplay) {
|
||||
const extra = document.createElement("li");
|
||||
extra.style.paddingTop = "10px";
|
||||
extra.style.marginBottom = "10px";
|
||||
extra.classList.add("upload-progress-extra");
|
||||
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);
|
||||
}
|
||||
progressContainer.appendChild(list);
|
||||
@@ -157,7 +138,7 @@ export function initUpload() {
|
||||
});
|
||||
}
|
||||
|
||||
// Submit handler remains unchanged.
|
||||
// Submit handler.
|
||||
if (uploadForm) {
|
||||
uploadForm.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user