Edwin Noorlander 60276cdccd Fix security vulnerabilities, remove dead code, and improve code quality
- 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
2026-02-16 15:05:27 +01:00

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');
});
});
});