- Fix path traversal with realpath() validation in getPage() and executePhpFile() - Remove insecure JWT secret fallback, require JWT_SECRET env var - Fix IP spoofing by only trusting proxy headers from configured proxies - Add Secure/HttpOnly/SameSite flags to all cookies - Use env var for debug mode instead of hardcoded true - Fix operator precedence bug in MQTTTracker track_user_flows check - Remove dead code: duplicate is_dir() block, unused scanForPageNames() - Remove htmlspecialchars() from filesystem path operations - Remove duplicate require_once calls and redundant autoloader includes - Fix unclosed </div> in getDirectoryListing() - Escape breadcrumb titles and add lang param to search result URLs - Make language prefixes dynamic from config instead of hardcoded nl|en - Make HTML lang attribute dynamic, add go_to translation key - Add aria-label/aria-expanded to sidebar toggle for accessibility - Fix event listener leak in app.js using event delegation - Remove console.log from production code - Update guides (NL/EN) with sidebar toggle documentation - Add TODO.md documenting all identified improvements
95 lines
3.2 KiB
JavaScript
95 lines
3.2 KiB
JavaScript
// Main application JavaScript
|
|
// This file contains general application functionality
|
|
|
|
/**
|
|
* Toggle sidebar visibility (open/close)
|
|
*/
|
|
function toggleSidebar() {
|
|
const sidebar = document.getElementById('site-sidebar');
|
|
const contentCol = sidebar ? sidebar.nextElementSibling || sidebar.parentElement.querySelector('.content-column') : null;
|
|
const btn = document.querySelector('.sidebar-toggle-btn');
|
|
const icon = btn ? btn.querySelector('i') : null;
|
|
|
|
if (!sidebar) return;
|
|
|
|
sidebar.classList.toggle('sidebar-hidden');
|
|
|
|
// Adjust content column width, toggle icon, and update aria-expanded
|
|
if (sidebar.classList.contains('sidebar-hidden')) {
|
|
if (contentCol) {
|
|
contentCol.classList.remove('col-lg-9', 'col-md-8');
|
|
contentCol.classList.add('col-12');
|
|
}
|
|
if (icon) {
|
|
icon.classList.remove('bi-layout-sidebar-inset');
|
|
icon.classList.add('bi-layout-sidebar');
|
|
}
|
|
if (btn) {
|
|
btn.setAttribute('aria-expanded', 'false');
|
|
}
|
|
sessionStorage.setItem('sidebarHidden', 'true');
|
|
} else {
|
|
if (contentCol) {
|
|
contentCol.classList.remove('col-12');
|
|
contentCol.classList.add('col-lg-9', 'col-md-8');
|
|
}
|
|
if (icon) {
|
|
icon.classList.remove('bi-layout-sidebar');
|
|
icon.classList.add('bi-layout-sidebar-inset');
|
|
}
|
|
if (btn) {
|
|
btn.setAttribute('aria-expanded', 'true');
|
|
}
|
|
sessionStorage.setItem('sidebarHidden', 'false');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restore sidebar state from sessionStorage on page load
|
|
*/
|
|
function restoreSidebarState() {
|
|
if (sessionStorage.getItem('sidebarHidden') === 'true') {
|
|
const sidebar = document.getElementById('site-sidebar');
|
|
if (sidebar) {
|
|
toggleSidebar();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize application when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Restore sidebar state
|
|
restoreSidebarState();
|
|
|
|
// Handle nested dropdowns for touch devices using event delegation
|
|
document.addEventListener('click', function(e) {
|
|
const toggle = e.target.closest('.dropdown-submenu .dropdown-toggle');
|
|
|
|
if (toggle) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const submenu = toggle.closest('.dropdown-submenu');
|
|
const dropdown = submenu.querySelector('.dropdown-menu');
|
|
|
|
// Close other submenus at the same level
|
|
const parent = submenu.parentElement;
|
|
parent.querySelectorAll('.dropdown-submenu').forEach(function(sibling) {
|
|
if (sibling !== submenu) {
|
|
var siblingMenu = sibling.querySelector('.dropdown-menu');
|
|
if (siblingMenu) siblingMenu.classList.remove('show');
|
|
}
|
|
});
|
|
|
|
// Toggle current submenu
|
|
if (dropdown) dropdown.classList.toggle('show');
|
|
return;
|
|
}
|
|
|
|
// Close all open submenus when clicking outside
|
|
document.querySelectorAll('.dropdown-submenu .dropdown-menu.show').forEach(function(menu) {
|
|
menu.classList.remove('show');
|
|
});
|
|
});
|
|
});
|