multi_static_website/home/sidebar.html
2025-12-23 13:06:25 +01:00

456 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Siti vari</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link rel="apple-touch-icon" sizes="57x57" href="/myicons/favicon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/myicons/favicon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/myicons/favicon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/myicons/favicon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/myicons/favicon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/myicons/favicon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/myicons/favicon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/myicons/favicon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/myicons/favicon-180x180.png">
<link rel="icon" type="image/png" sizes="16x16" href="/myicons/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="/myicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/myicons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="192x192" href="/myicons/favicon-192x192.png">
<link rel="shortcut icon" type="image/x-icon" href="/myicons/favicon.ico">
<link rel="icon" type="image/x-icon" href="/myicons/favicon.ico">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="/myicons/favicon-144x144.png">
<meta name="msapplication-config" content="/myicons/browserconfig.xml">
<link rel="manifest" href="/myicons/manifest.json">
<meta name="theme-color" content="#ffffff">
<style>
:root {
--sidebar-w: 260px;
--sidebar-bg: #0f172a;
--sidebar-fg: #e2e8f0;
--border: #1f2937;
--overlay-bg: rgba(0,0,0,0.5);
--transition: 250ms ease;
--focus: #f59e0b;
}
/* Base */
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
font-family: system-ui, sans-serif;
background: radial-gradient(1200px 700px at 20% 10%, #0b1220 0%, #070c1a 50%, #050a16 100%);
color: #cbd5e1;
}
/* Icona menù */
.menu-btn {
position: fixed;
top: 12px;
left: 12px;
z-index: 100;
background: transparent;
border: none;
cursor: pointer;
color: #e2e8f0;
padding: 4px;
transition: opacity var(--transition);
}
.menu-btn.hidden { opacity: 0; pointer-events: none; }
.menu-btn.dragging { cursor: grabbing; }
.menu-btn svg { pointer-events: none; }
/* Sidebar */
.sidebar {
position: fixed;
top: 0;
left: 0;
width: var(--sidebar-w);
height: 100%;
background: linear-gradient(180deg, #0f172a 0%, #0c1423 100%);
color: var(--sidebar-fg);
transform: translateX(-100%);
transition: transform var(--transition);
z-index: 90;
padding: 16px 12px;
border-right: 1px solid var(--border);
}
.sidebar.open { transform: translateX(0); }
.nav-header { display: flex; align-items: center; }
.nav-title {
font-size: .8rem;
text-transform: uppercase;
letter-spacing: .08em;
color: #94a3b8;
margin: 8px 8px 4px;
}
.nav-header {
display: flex;
align-items: center;
justify-content: space-between; /* titolo a sinistra, bottone a destra */
padding: 0 8px;
margin-bottom: 8px;
}
.nav-title {
margin: 0; /* reset margini per allineamento */
}
.refresh-btn {
background: transparent;
border: none;
cursor: pointer;
color: var(--sidebar-fg);
padding: 4px;
transition: transform var(--transition), color var(--transition);
}
.refresh-btn:hover {
transform: rotate(90deg);
color: var(--focus);
}
.edit-btn {
background: transparent;
border: none;
cursor: pointer;
color: var(--sidebar-fg);
padding: 4px;
transition: transform var(--transition), color var(--transition);
}
.edit-btn:hover {
transform: rotate(90deg);
color: var(--focus);
}
.nav-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.nav-link {
display: block;
width: 100%;
text-align: left;
padding: 10px 12px;
border-radius: 8px;
color: var(--sidebar-fg);
background: rgba(255,255,255,0.02);
border: 1px solid transparent;
transition: background var(--transition), transform var(--transition), border-color var(--transition);
cursor: pointer;
}
.nav-link:hover {
background: rgba(255,255,255,0.06);
transform: translateX(2px);
border-color: #263145;
}
.nav-link:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 3px;
}
/* Overlay */
.overlay {
position: fixed;
inset: 0;
background: var(--overlay-bg);
backdrop-filter: blur(2px);
z-index: 80;
display: none;
}
.overlay.show { display: block; }
/* Contenuto principale */
.content {
margin: 0;
padding: 0;
height: 100dvh; /* viewport dinamico, elimina banda nera su mobile */
}
.frame-wrap {
position: relative;
width: 100%;
height: 100%;
}
iframe {
width: 100%;
height: 100%;
border: none;
margin: 0;
display: block;
background: #fff; /* evita bleed del gradiente */
}
/* Fallback per browser che non supportano 100dvh */
@supports not (height: 100dvh) {
.content { height: 100vh; }
}
.frame-error {
position: absolute;
inset: 0;
display: none;
align-items: center;
justify-content: center;
padding: 20px;
color: #ef4444;
background: rgba(0,0,0,0.25);
backdrop-filter: blur(2px);
text-align: center;
}
.frame-error.show { display: flex; }
</style>
</head>
<body>
<!-- Icona menù -->
<button id="menuBtn" class="menu-btn" aria-label="Apri menù">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="3" width="20" height="2" fill="currentColor"/>
<rect x="2" y="7" width="20" height="2" fill="currentColor"/>
<rect x="2" y="11" width="20" height="2" fill="currentColor"/>
<rect x="2" y="15" width="20" height="2" fill="currentColor"/>
<rect x="2" y="19" width="20" height="2" fill="currentColor"/>
</svg>
</button>
<!-- Sidebar
<aside id="sidebar" class="sidebar" aria-hidden="true">
<nav class="nav" id="navRoot">
<h2 class="nav-title">Sites</h2>
<ul id="siteList" class="nav-list"></ul>
</nav>
</aside> -->
<!-- Sidebar -->
<aside id="sidebar" class="sidebar" aria-hidden="true">
<nav class="nav" id="navRoot">
<div class="nav-header">
<h2 class="nav-title">Sites</h2>
<div class="nav-actions">
<button id="refreshBtn" class="refresh-btn" aria-label="Ricarica">
<i class="fas fa-sync-alt"></i>
</button>
<button id="editBtn" class="edit-btn" aria-label="Edit">
<i class="fa-solid fa-gear"></i>
</button>
</div>
</div>
<ul id="siteList" class="nav-list"></ul>
</nav>
</aside>
<!-- Overlay -->
<div id="overlay" class="overlay"></div>
<!-- Contenuto principale -->
<main class="content">
<div class="frame-wrap">
<iframe id="contentFrame" referrerpolicy="no-referrer"></iframe>
<div id="frameError" class="frame-error">
<div>
<strong>Il sito non può essere caricato in iframe.</strong><br />
Potrebbe avere X-Frame-Options o Content-Security-Policy che ne impediscono lincorporamento.
</div>
</div>
</div>
</main>
<script>
const menuBtn = document.getElementById('menuBtn');
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('overlay');
const siteList = document.getElementById('siteList');
const iframe = document.getElementById('contentFrame');
const frameErr = document.getElementById('frameError');
const navRoot = document.getElementById('navRoot');
const refreshBtn = document.getElementById('refreshBtn');
function toggleSidebar() {
const isOpen = sidebar.classList.toggle('open');
overlay.classList.toggle('show', isOpen);
sidebar.setAttribute('aria-hidden', String(!isOpen));
menuBtn.classList.toggle('hidden', isOpen);
if (!isOpen) {
document.activeElement.blur();
}
}
overlay.addEventListener('click', toggleSidebar);
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && sidebar.classList.contains('open')) toggleSidebar();
});
/* Evita che i click dentro la sidebar propaghino al document (prevenendo chiusure involontarie) */
navRoot.addEventListener('click', (e) => e.stopPropagation());
navRoot.addEventListener('mousedown', (e) => e.stopPropagation());
navRoot.addEventListener('touchstart', (e) => e.stopPropagation(), { passive: false });
// Long press drag&drop — isolato SOLO al pulsante menù
let pressTimer, isDragging = false, offsetX, offsetY, pressActive = false;
function startPress(e) {
e.preventDefault();
pressActive = true;
const point = e.touches ? e.touches[0] : e;
pressTimer = setTimeout(() => {
isDragging = true;
menuBtn.classList.add('dragging');
offsetX = point.clientX - menuBtn.offsetLeft;
offsetY = point.clientY - menuBtn.offsetTop;
}, 400);
/* Attiva listeners di fine solo quando la press parte sul pulsante */
window.addEventListener('mouseup', endPressOnce);
window.addEventListener('touchend', endPressOnce);
window.addEventListener('mousemove', moveBtn);
window.addEventListener('touchmove', moveBtn, { passive: false });
}
function endPressCore() {
clearTimeout(pressTimer);
if (isDragging) {
isDragging = false;
menuBtn.classList.remove('dragging');
} else if (pressActive) {
// click breve sul pulsante → toggle menù
toggleSidebar();
}
pressActive = false;
}
function endPressOnce(e) {
endPressCore();
// pulizia listeners temporanei
window.removeEventListener('mouseup', endPressOnce);
window.removeEventListener('touchend', endPressOnce);
window.removeEventListener('mousemove', moveBtn);
window.removeEventListener('touchmove', moveBtn);
}
function moveBtn(e) {
if (!isDragging) return;
const point = e.touches ? e.touches[0] : e;
menuBtn.style.left = (point.clientX - offsetX) + 'px';
menuBtn.style.top = (point.clientY - offsetY) + 'px';
}
menuBtn.addEventListener('mousedown', startPress);
menuBtn.addEventListener('touchstart', startPress, { passive: false });
// Helper per URL pulite
function joinUrl(base, path) {
const cleanBase = String(base).replace(/\/+$/,'');
const cleanPath = String(path).replace(/^\/+/,'');
return cleanBase + '/' + cleanPath;
}
async function refreshSideBar() {
//if (doRefresh) {
try {
const res = await fetch('/config.json', { cache: 'no-cache' });
if (!res.ok) throw new Error('config.json non raggiungibile: ' + res.status);
const cfg = await res.json();
if (!cfg || !Array.isArray(cfg.s) || !cfg.url) {
throw new Error('Struttura config non valida. Attesi: { url: string, sites: string[] }');
}
siteList.innerHTML = '';
cfg.s.forEach(site => {
const li = document.createElement('li');
const btn = document.createElement('button');
btn.type = 'button';
btn.textContent = site.name;
btn.className = 'nav-link';
btn.addEventListener('click', (e) => {
e.stopPropagation(); /* il click resta nel menù */
const target = joinUrl(cfg.url, site.dir);
openInFrame(target);
toggleSidebar();
});
li.appendChild(btn);
siteList.appendChild(li);
});
//doRefresh =false;
return cfg;
} catch (err) {
//doRefresh =false;
frameErr.classList.add('show');
frameErr.innerHTML = '<div><strong>Errore di configurazione.</strong><br />' +
(err && err.message ? err.message : 'Impossibile caricare config.json') + '</div>';
}
//}
}
window.addEventListener("message", (event) => {
if (event.data === "refreshSideBar") {
refreshSideBar();
}
});
// Caricamento sites da config.json
(async function loadConfig() {
try {
let cfg = await refreshSideBar();
refreshBtn.addEventListener('click', () => {
// ricarica liframe
iframe.src = iframe.src.split('?')[0] + '?t=' + Date.now();
toggleSidebar();
});
editBtn.addEventListener('click', () => {
// carica /config
const target = joinUrl(cfg.url, "settings");
openInFrame(target);
toggleSidebar();
doRefresh = true;
});
// Sito iniziale
openInFrame(joinUrl(cfg.url, cfg.s[0].dir));
} catch (err) {
frameErr.classList.add('show');
frameErr.innerHTML = '<div><strong>Errore di configurazione.</strong><br />' +
(err && err.message ? err.message : 'Impossibile caricare config.json') + '</div>';
}
})();
// Carica URL nelliframe e gestisce casi di blocco
function openInFrame(url) {
frameErr.classList.remove('show');
// Mixed content
const pageIsHttps = location.protocol === 'https:';
const urlIsHttp = /^http:/.test(url);
if (pageIsHttps && urlIsHttp) {
frameErr.classList.add('show');
frameErr.innerHTML = '<div><strong>Contenuto misto bloccato.</strong><br />Pagina HTTPS, sito HTTP. Usa HTTPS.</div>';
return;
}
// Caricamento con timeout (indicativo di X-Frame-Options/CSP)
iframe.src = url;
const timeout = setTimeout(() => {
frameErr.classList.add('show');
frameErr.innerHTML = '<div><strong>Il sito non può essere caricato in iframe.</strong><br />Verifica X-Frame-Options/CSP (frame-ancestors).</div>';
}, 6000);
iframe.onload = () => {
clearTimeout(timeout);
frameErr.classList.remove('show');
};
iframe.onerror = () => {
clearTimeout(timeout);
frameErr.classList.add('show');
frameErr.innerHTML = '<div><strong>Errore di caricamento.</strong><br />Verifica che lURL sia raggiungibile.</div>';
};
}
</script>
</body>
</html>