Struttura: il sito esistente si sposta sotto apps/sito
Primo passo della separazione sito / piattaforme decisa da Adriano il 03/09: due applicazioni autosufficienti, senza workspace di root, cosi' che ognuna resti un progetto Astro completo ed estraibile. Qui non cambia niente del codice: solo la radice. I pattern del .gitignore che avevano una barra erano ancorati alla radice e dopo lo spostamento non avrebbero piu' coperto niente (data/*.db*, uploads/*): resi validi a qualunque profondita'. Suite: 45 file, 347 test verdi prima e dopo lo spostamento. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EndnceRA5WnA6rvA5iV9WL
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
---
|
||||
import { site } from '../data/site';
|
||||
import { piattaforme } from '../data/piattaforme';
|
||||
import { t, contentImageUrl } from '../lib/content';
|
||||
import { landingFor } from '../lib/auth';
|
||||
interface Props {
|
||||
user: { username: string; role: string } | null;
|
||||
showTags: boolean;
|
||||
canTags: boolean;
|
||||
}
|
||||
const { user, canTags, showTags } = Astro.props;
|
||||
const path = Astro.url.pathname;
|
||||
const loginHref = `/login?next=${encodeURIComponent(path)}`;
|
||||
const areaHref = user ? landingFor(user.role) : '/admin';
|
||||
// Le piattaforme riservate compaiono nel menu solo a chi può davvero aprirle.
|
||||
const canPiattaforme = user?.role === 'piattaforme' || user?.role === 'admin';
|
||||
const inPiattaforme = piattaforme.some((p) => path === p.href || path.startsWith(`${p.href}/`));
|
||||
---
|
||||
<header class="hdr" id="site-header">
|
||||
<div class="container hdr__in">
|
||||
<a href="/" class="hdr__logo"><img src={contentImageUrl('header.logo.image', '/img/logo-dark.png')} alt={t('header.logo.alt')} width="150" /></a>
|
||||
<nav class="hdr__nav" id="site-nav" aria-label="principale">
|
||||
{site.navigation.map((item, i) => (
|
||||
(!('adminOnly' in item && item.adminOnly) || user?.role === 'admin') && (
|
||||
<a href={item.href} class:list={['hdr__link', { 'is-active': path === item.href || (item.href !== '/' && path.startsWith(item.href)) }]}>{t(`global.nav.${i + 1}.label`)}</a>
|
||||
)
|
||||
))}
|
||||
{canPiattaforme && (
|
||||
<details class="plat">
|
||||
<summary class:list={['hdr__link', 'hdr__link--bio', 'plat__btn', { 'is-active': inPiattaforme }]}>Piattaforme ▾</summary>
|
||||
<div class="plat__panel">
|
||||
{piattaforme.map((p) => (
|
||||
<a href={p.href} class:list={['plat__item', { 'is-active': path === p.href || path.startsWith(`${p.href}/`) }]}>{p.label}</a>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
</nav>
|
||||
<div class="hdr__user">
|
||||
{user ? (
|
||||
<details class="umenu">
|
||||
<summary class="umenu__btn">{user.username} ▾</summary>
|
||||
<div class="umenu__panel">
|
||||
<a href={areaHref}>Area riservata</a>
|
||||
{canTags && (
|
||||
<button type="button" class="umenu__tags" id="toggle-tags" data-on={showTags ? '1' : '0'}>{showTags ? 'Nascondi tag' : 'Mostra tag'}</button>
|
||||
)}
|
||||
<a href="/admin/logout">Esci</a>
|
||||
</div>
|
||||
</details>
|
||||
) : (
|
||||
<a class="hdr__login" href={loginHref}>Accedi</a>
|
||||
)}
|
||||
</div>
|
||||
<button class="hdr__burger" id="nav-toggle" aria-label="Apri menu" aria-expanded="false">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
.hdr { position: sticky; top: 0; z-index: 100; background: #fff; transition: box-shadow .2s; }
|
||||
.hdr.is-scrolled { box-shadow: 0 2px 14px rgba(0,0,0,.08); }
|
||||
.hdr__in { display: flex; align-items: center; justify-content: space-between; height: var(--header-h); }
|
||||
.hdr__nav { display: flex; gap: 34px; }
|
||||
.hdr__link { font-family: var(--font-heading); font-size: .72rem; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; text-decoration: none; color: var(--c-heading); transition: color .3s ease; }
|
||||
.hdr__link:hover, .hdr__link.is-active { color: var(--c-accent-dark); }
|
||||
.hdr__link--bio { color: var(--c-accent-dark); }
|
||||
/* Menu Piattaforme: su desktop è un pannello che scende dalla voce, in mobile —
|
||||
dove la nav è già una colonna — le voci si aprono in linea sotto il titolo. */
|
||||
.plat { position: relative; }
|
||||
.plat__btn { cursor: pointer; list-style: none; }
|
||||
.plat__btn::-webkit-details-marker { display: none; }
|
||||
.plat__panel {
|
||||
position: absolute; left: 0; top: calc(100% + 14px); background: #fff; min-width: 210px;
|
||||
box-shadow: 0 8px 22px rgba(0,0,0,.12); display: flex; flex-direction: column; padding: 8px 0; z-index: 200;
|
||||
}
|
||||
.plat__item { padding: 10px 16px; font-size: .8rem; text-decoration: none; color: var(--c-heading); }
|
||||
.plat__item:hover, .plat__item.is-active { background: #f4f2ef; color: var(--c-accent-dark); }
|
||||
.hdr__burger { display: none; background: none; border: 0; cursor: pointer; padding: 8px; }
|
||||
.hdr__burger span { display: block; width: 22px; height: 2px; background: var(--c-heading); margin: 5px 0; }
|
||||
@media (max-width: 991px) {
|
||||
.hdr__nav { display: none; position: absolute; top: var(--header-h); left: 0; right: 0; background: #fff; flex-direction: column; gap: 0; padding: 10px 20px 20px; box-shadow: 0 10px 20px rgba(0,0,0,.08); }
|
||||
.hdr__nav.is-open { display: flex; }
|
||||
.hdr__link { padding: 12px 0; }
|
||||
.hdr__burger { display: block; }
|
||||
.plat__panel { position: static; box-shadow: none; padding: 0 0 8px; min-width: 0; }
|
||||
.plat__item { padding: 10px 0 10px 14px; }
|
||||
}
|
||||
.hdr__user { display: flex; align-items: center; }
|
||||
.hdr__login { font-family: var(--font-heading); font-size: .72rem; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; text-decoration: none; color: var(--c-heading); }
|
||||
.hdr__login:hover { color: var(--c-accent-dark); }
|
||||
.umenu { position: relative; }
|
||||
.umenu__btn { cursor: pointer; list-style: none; font-family: var(--font-heading); font-size: .72rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; color: var(--c-heading); }
|
||||
.umenu__btn::-webkit-details-marker { display: none; }
|
||||
.umenu__panel { position: absolute; right: 0; top: calc(100% + 8px); background: #fff; box-shadow: 0 8px 22px rgba(0,0,0,.12); min-width: 180px; display: flex; flex-direction: column; padding: 8px 0; z-index: 200; }
|
||||
.umenu__panel a, .umenu__tags { padding: 10px 16px; text-align: left; background: none; border: 0; cursor: pointer; font: inherit; font-size: .8rem; text-decoration: none; color: var(--c-heading); }
|
||||
.umenu__panel a:hover, .umenu__tags:hover { background: #f4f2ef; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const header = document.getElementById('site-header')!;
|
||||
const toggle = document.getElementById('nav-toggle')!;
|
||||
const nav = document.getElementById('site-nav')!;
|
||||
addEventListener('scroll', () => header.classList.toggle('is-scrolled', scrollY > 10), { passive: true });
|
||||
toggle.addEventListener('click', () => {
|
||||
const open = nav.classList.toggle('is-open');
|
||||
toggle.setAttribute('aria-expanded', String(open));
|
||||
});
|
||||
|
||||
// Il pannello Piattaforme è un <details>: da solo resterebbe aperto anche cliccando altrove.
|
||||
const plat = document.querySelector<HTMLDetailsElement>('.plat');
|
||||
if (plat) {
|
||||
document.addEventListener('click', (e) => {
|
||||
if (plat.open && !plat.contains(e.target as Node)) plat.open = false;
|
||||
});
|
||||
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') plat.open = false; });
|
||||
}
|
||||
|
||||
const tagsBtn = document.getElementById('toggle-tags');
|
||||
if (tagsBtn) {
|
||||
const on = tagsBtn.dataset.on === '1';
|
||||
tagsBtn.addEventListener('click', async () => {
|
||||
const res = await fetch('/api/admin/showtags', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ on: !on }),
|
||||
});
|
||||
if (res.ok) location.reload();
|
||||
else alert('Operazione non riuscita.');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user