persistent folder tree & adjustments

This commit is contained in:
Ryan
2025-03-15 18:12:31 -04:00
committed by GitHub
parent 2764605ae9
commit e43732bf38
3 changed files with 72 additions and 22 deletions

View File

@@ -52,7 +52,7 @@ function parseCustomDate(dateStr) {
// Determines if a file is editable based on its extension. // Determines if a file is editable based on its extension.
export function canEditFile(fileName) { export function canEditFile(fileName) {
const allowedExtensions = [ const allowedExtensions = [
"txt", "html", "htm", "php", "css", "js", "json", "xml", "txt", "html", "htm", "css", "js", "json", "xml",
"md", "py", "ini", "csv", "log", "conf", "config", "bat", "md", "py", "ini", "csv", "log", "conf", "config", "bat",
"rtf", "doc", "docx" "rtf", "doc", "docx"
]; ];
@@ -147,9 +147,18 @@ export function renderFileTable(folder) {
// Get persistent items per page from localStorage. // Get persistent items per page from localStorage.
const itemsPerPageSetting = parseInt(localStorage.getItem('itemsPerPage') || '10', 10); const itemsPerPageSetting = parseInt(localStorage.getItem('itemsPerPage') || '10', 10);
const currentPage = window.currentPage || 1;
// Use a mutable currentPage variable
let currentPage = window.currentPage || 1;
const totalFiles = filteredFiles.length; const totalFiles = filteredFiles.length;
const totalPages = Math.ceil(totalFiles / itemsPerPageSetting); const totalPages = Math.ceil(totalFiles / itemsPerPageSetting);
// If the current page is greater than totalPages, reset it to a valid page (for example, page 1 or totalPages)
if (currentPage > totalPages) {
currentPage = totalPages > 0 ? totalPages : 1;
window.currentPage = currentPage;
}
const safeSearchTerm = escapeHTML(searchTerm); const safeSearchTerm = escapeHTML(searchTerm);
const topControlsHTML = ` const topControlsHTML = `
@@ -202,13 +211,13 @@ export function renderFileTable(folder) {
const safeSize = escapeHTML(file.size); const safeSize = escapeHTML(file.size);
const safeUploader = escapeHTML(file.uploader || "Unknown"); const safeUploader = escapeHTML(file.uploader || "Unknown");
const isViewable = /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic|pdf|mp4|webm|ogg)$/i.test(file.name); const isViewable = /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic|pdf|mp4|webm|mov|ogg)$/i.test(file.name);
let previewButton = ""; let previewButton = "";
if (isViewable) { if (isViewable) {
let previewIcon = ""; let previewIcon = "";
if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic)$/i.test(file.name)) { if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tif|tiff|eps|heic)$/i.test(file.name)) {
previewIcon = `<i class="material-icons">image</i>`; previewIcon = `<i class="material-icons">image</i>`;
} else if (/\.(mp4|webm|ogg)$/i.test(file.name)) { } else if (/\.(mp4|webm|mov|ogg)$/i.test(file.name)) {
previewIcon = `<i class="material-icons">videocam</i>`; previewIcon = `<i class="material-icons">videocam</i>`;
} else if (/\.pdf$/i.test(file.name)) { } else if (/\.pdf$/i.test(file.name)) {
previewIcon = `<i class="material-icons">picture_as_pdf</i>`; previewIcon = `<i class="material-icons">picture_as_pdf</i>`;
@@ -354,7 +363,7 @@ window.previewFile = function (fileUrl, fileName) {
embed.style.height = "80vh"; embed.style.height = "80vh";
embed.style.border = "none"; embed.style.border = "none";
container.appendChild(embed); container.appendChild(embed);
} else if (/\.(mp4|webm|ogg)$/i.test(fileName)) { } else if (/\.(mp4|webm|mov|ogg)$/i.test(fileName)) {
// Video preview using <video> // Video preview using <video>
const video = document.createElement("video"); const video = document.createElement("video");
video.src = fileUrl; video.src = fileUrl;
@@ -762,7 +771,6 @@ export function editFile(fileName, folder) {
const isDarkMode = document.body.classList.contains("dark-mode"); const isDarkMode = document.body.classList.contains("dark-mode");
const theme = isDarkMode ? "material-darker" : "default"; const theme = isDarkMode ? "material-darker" : "default";
// Initialize CodeMirror
// Initialize CodeMirror // Initialize CodeMirror
const editor = CodeMirror.fromTextArea(document.getElementById("fileEditor"), { const editor = CodeMirror.fromTextArea(document.getElementById("fileEditor"), {
lineNumbers: true, lineNumbers: true,

View File

@@ -35,28 +35,58 @@ function buildFolderTree(folders) {
return tree; return tree;
} }
// ----------------------
// Session State for Folder Tree
// ----------------------
function loadFolderTreeState() {
const state = sessionStorage.getItem("folderTreeState");
return state ? JSON.parse(state) : {};
}
function saveFolderTreeState(state) {
sessionStorage.setItem("folderTreeState", JSON.stringify(state));
}
// ----------------------
// Folder Deletion Helper
// ----------------------
function getParentFolder(folder) {
if (folder === "root") return "root";
const lastSlash = folder.lastIndexOf("/");
return lastSlash === -1 ? "root" : folder.substring(0, lastSlash);
}
// ----------------------
// Render Folder Tree
// ----------------------
/** /**
* Render the folder tree as nested <ul> elements using CSS classes. * Render the folder tree as nested <ul> elements using CSS classes.
* The open/closed state of each folder is restored from session storage.
* @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 = "block") { function renderFolderTree(tree, parentPath = "", defaultDisplay = "block") {
// Determine display class based on defaultDisplay value. // Use the stored state (if any) for each folder.
const displayClass = defaultDisplay === 'none' ? 'collapsed' : 'expanded'; const state = loadFolderTreeState();
let html = `<ul class="folder-tree ${displayClass}">`; let html = `<ul class="folder-tree ${defaultDisplay === 'none' ? 'collapsed' : 'expanded'}">`;
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;
// Use saved state if exists; otherwise use the defaultDisplay.
const displayState = state[fullPath] !== undefined ? state[fullPath] : defaultDisplay;
const ulClass = displayState === "none" ? "collapsed" : "expanded";
html += `<li class="folder-item">`; html += `<li class="folder-item">`;
if (hasChildren) { if (hasChildren) {
html += `<span class="folder-toggle">[+]</span>`; const toggleSymbol = (displayState === "none") ? "[+]" : "[-]";
// Add a data-folder attribute to track which folder is toggled.
html += `<span class="folder-toggle" data-folder="${fullPath}">${toggleSymbol}</span>`;
} else { } else {
html += `<span class="folder-indent-placeholder"></span>`; html += `<span class="folder-indent-placeholder"></span>`;
} }
html += `<span class="folder-option" data-folder="${fullPath}">${folder}</span>`; html += `<span class="folder-option" data-folder="${fullPath}">${folder}</span>`;
if (hasChildren) { if (hasChildren) {
html += renderFolderTree(tree[folder], fullPath, "none"); html += renderFolderTree(tree[folder], fullPath, displayState);
} }
html += `</li>`; html += `</li>`;
} }
@@ -83,6 +113,10 @@ function expandTreePath(path) {
const toggle = li.querySelector(".folder-toggle"); const toggle = li.querySelector(".folder-toggle");
if (toggle) { if (toggle) {
toggle.textContent = "[-]"; toggle.textContent = "[-]";
// Also update session state.
let state = loadFolderTreeState();
state[cumulative] = "block";
saveFolderTreeState(state);
} }
} }
} }
@@ -92,7 +126,6 @@ function expandTreePath(path) {
// ---------------------- // ----------------------
// Main Interactive Tree // Main Interactive Tree
// ---------------------- // ----------------------
export async function loadFolderTree(selectedFolder) { export async function loadFolderTree(selectedFolder) {
try { try {
const response = await fetch('getFolderList.php'); const response = await fetch('getFolderList.php');
@@ -118,7 +151,7 @@ export async function loadFolderTree(selectedFolder) {
let html = ""; let html = "";
// Build the root row. // Build the root row.
html += `<div id="rootRow" class="root-row"> html += `<div id="rootRow" class="root-row">
<span class="folder-toggle">[-]</span> <span class="folder-toggle" data-folder="root">[-]</span>
<span class="folder-option root-folder-option" data-folder="root">(Root)</span> <span class="folder-option root-folder-option" data-folder="root">(Root)</span>
</div>`; </div>`;
@@ -129,8 +162,8 @@ export async function loadFolderTree(selectedFolder) {
</li> </li>
</ul>`; </ul>`;
} else { } else {
const tree = buildFolderTree(folders); // your existing function const tree = buildFolderTree(folders); // build the tree from folder list
html += renderFolderTree(tree, "", "block"); // your existing function html += renderFolderTree(tree, "", "block");
} }
container.innerHTML = html; container.innerHTML = html;
@@ -164,22 +197,27 @@ export async function loadFolderTree(selectedFolder) {
}); });
}); });
// Attach toggle events (same as your original logic). // Attach toggle events.
// Special handling 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) {
let state = loadFolderTreeState();
if (nestedUl.classList.contains("collapsed") || !nestedUl.classList.contains("expanded")) { if (nestedUl.classList.contains("collapsed") || !nestedUl.classList.contains("expanded")) {
nestedUl.classList.remove("collapsed"); nestedUl.classList.remove("collapsed");
nestedUl.classList.add("expanded"); nestedUl.classList.add("expanded");
this.textContent = "[-]"; this.textContent = "[-]";
state["root"] = "block";
} else { } else {
nestedUl.classList.remove("expanded"); nestedUl.classList.remove("expanded");
nestedUl.classList.add("collapsed"); nestedUl.classList.add("collapsed");
this.textContent = "[+]"; this.textContent = "[+]";
state["root"] = "none";
} }
saveFolderTreeState(state);
} }
}); });
} }
@@ -188,16 +226,21 @@ export async function loadFolderTree(selectedFolder) {
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");
const folderPath = this.getAttribute("data-folder");
let state = loadFolderTreeState();
if (siblingUl) { if (siblingUl) {
if (siblingUl.classList.contains("collapsed") || !siblingUl.classList.contains("expanded")) { if (siblingUl.classList.contains("collapsed") || !siblingUl.classList.contains("expanded")) {
siblingUl.classList.remove("collapsed"); siblingUl.classList.remove("collapsed");
siblingUl.classList.add("expanded"); siblingUl.classList.add("expanded");
this.textContent = "[-]"; this.textContent = "[-]";
state[folderPath] = "block";
} else { } else {
siblingUl.classList.remove("expanded"); siblingUl.classList.remove("expanded");
siblingUl.classList.add("collapsed"); siblingUl.classList.add("collapsed");
this.textContent = "[+]"; this.textContent = "[+]";
state[folderPath] = "none";
} }
saveFolderTreeState(state);
} }
}); });
}); });
@@ -290,10 +333,9 @@ document.getElementById("confirmDeleteFolder").addEventListener("click", functio
.then(data => { .then(data => {
if (data.success) { if (data.success) {
showToast("Folder deleted successfully!"); showToast("Folder deleted successfully!");
if (window.currentFolder === selectedFolder) { // Set current folder to the parent folder, not root
window.currentFolder = "root"; window.currentFolder = getParentFolder(selectedFolder);
} loadFolderList(window.currentFolder);
loadFolderList("root");
} else { } else {
showToast("Error: " + (data.error || "Could not delete folder")); showToast("Error: " + (data.error || "Could not delete folder"));
} }

View File

@@ -1216,7 +1216,7 @@ body.dark-mode #fileListContainer {
=========================================================== */ =========================================================== */
.folder-tree { .folder-tree {
list-style-type: none; list-style-type: none;
padding-left: 20px; padding-left: 25px;
margin: 0; margin: 0;
} }
@@ -1240,7 +1240,7 @@ body.dark-mode #fileListContainer {
.folder-indent-placeholder { .folder-indent-placeholder {
display: inline-block; display: inline-block;
width: 18px; width: 0px;
} }
.folder-option { .folder-option {