persistent folder tree & adjustments
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user