Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bcc965655d | |||
| 8d593dce90 |
@@ -11,3 +11,7 @@ CONTACT_FROM=postmaster@insanitylab.it
|
||||
CONTACT_TO=info@insanitylab.it
|
||||
DB_PATH=data/insanitylab.db
|
||||
UPLOADS_DIR=uploads
|
||||
|
||||
# Google Tag Manager: ID container preso da tagmanager.google.com (accanto al nome container).
|
||||
# Formato GTM-XXXXXXX. Lascia vuoto per non caricare GTM (es. in locale/staging).
|
||||
GTM_ID=GTM-XXXXXXX
|
||||
|
||||
+77
-8
@@ -19,10 +19,17 @@ const user = tok ? getSessionUser(getDb(), tok) : null;
|
||||
const canTags = !!user && (user.role === 'superuser' || user.role === 'admin');
|
||||
const wantTags = Astro.cookies.get('showtags')?.value === '1' || Astro.url.searchParams.get('annotate') === '1';
|
||||
const showTags = canTags && wantTags;
|
||||
|
||||
// Google Tag Manager: l'ID container arriva da GTM_ID (.env), letto a runtime via process.env
|
||||
// perché in SSR le PUBLIC_* di Vite verrebbero inlinate a build-time. Se vuoto GTM non carica.
|
||||
const gtmId = process.env.GTM_ID;
|
||||
---
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
{gtmId && (
|
||||
<script is:inline set:html={`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','${gtmId}');`} />
|
||||
)}
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{title} — InsanityLab</title>
|
||||
@@ -36,6 +43,9 @@ const showTags = canTags && wantTags;
|
||||
<script is:inline>document.documentElement.classList.add('js');</script>
|
||||
</head>
|
||||
<body>
|
||||
{gtmId && (
|
||||
<noscript set:html={`<iframe src="https://www.googletagmanager.com/ns.html?id=${gtmId}" height="0" width="0" style="display:none;visibility:hidden"></iframe>`} />
|
||||
)}
|
||||
<Header user={user} showTags={showTags} canTags={canTags} />
|
||||
<main><slot /></main>
|
||||
<Footer />
|
||||
@@ -103,6 +113,8 @@ const showTags = canTags && wantTags;
|
||||
font: inherit; font-size: .9rem; color: #2b2320; cursor: pointer; }
|
||||
.tm-field select:focus { outline: 2px solid #9c8b70; outline-offset: 1px; border-color: #9c8b70; }
|
||||
.tm-field select.is-set { border-color: #9c8b70; background-color: #f6f1e8; font-weight: 600; }
|
||||
.tm-field select.is-estimated { border-style: dashed; border-color: #c4b79f; color: #6b5b48; }
|
||||
.tm-note { margin: 12px 0 0; font: 400 12px/1.5 system-ui, sans-serif; color: #8a7c68; }
|
||||
.tag-modal__box input[type=file] { display: block; width: 100%; font: inherit; font-size: .9rem; padding: 10px;
|
||||
border: 1px dashed #cbbfad; border-radius: 10px; background: #fff; box-sizing: border-box; }
|
||||
.tm-del { margin-top: 12px; padding: 8px 14px; border: 0; border-radius: 8px; background: #b4443a; color: #fff; cursor: pointer; font: 600 .85rem system-ui, sans-serif; }
|
||||
@@ -141,6 +153,42 @@ const showTags = canTags && wantTags;
|
||||
color: { accent: 'Accento', 'accent-dark': 'Accento scuro', dark: 'Scuro', heading: 'Titolo', text: 'Testo', 'text-light': 'Testo chiaro', white: 'Bianco' },
|
||||
};
|
||||
const valLabel = (g, v) => (VALUE_LABELS[g] && VALUE_LABELS[g][v]) || v;
|
||||
|
||||
// Stima il preset più vicino all'aspetto REALE dell'elemento (getComputedStyle).
|
||||
const px = (s) => parseFloat(s) || 0;
|
||||
const rgb = (s) => { const m = String(s).match(/-?\d+(\.\d+)?/g); return m ? m.slice(0, 3).map(Number) : null; };
|
||||
const SIZE_SCALE = { s: 0.85, m: 1, l: 1.25, xl: 1.5, xxl: 2 };
|
||||
let colorProbe = null;
|
||||
function nearestSize(el) {
|
||||
const cs = getComputedStyle(el);
|
||||
const ref = el.parentElement ? px(getComputedStyle(el.parentElement).fontSize) : px(cs.fontSize);
|
||||
const ratio = ref ? px(cs.fontSize) / ref : 1;
|
||||
let best = 'm', bd = Infinity;
|
||||
for (const k in SIZE_SCALE) { const d = Math.abs(SIZE_SCALE[k] - ratio); if (d < bd) { bd = d; best = k; } }
|
||||
return best;
|
||||
}
|
||||
function nearestColor(el) {
|
||||
const a = rgb(getComputedStyle(el).color); if (!a) return null;
|
||||
if (!colorProbe) { colorProbe = document.createElement('span'); colorProbe.style.cssText = 'position:absolute;left:-9999px;visibility:hidden'; document.body.appendChild(colorProbe); }
|
||||
let best = null, bd = Infinity;
|
||||
STYLE_GROUPS.color.forEach((c) => {
|
||||
colorProbe.className = 'cs-color-' + c;
|
||||
const p = rgb(getComputedStyle(colorProbe).color); if (!p) return;
|
||||
const d = (p[0]-a[0])**2 + (p[1]-a[1])**2 + (p[2]-a[2])**2;
|
||||
if (d < bd) { bd = d; best = c; }
|
||||
});
|
||||
colorProbe.className = '';
|
||||
return best;
|
||||
}
|
||||
function effective(group, el) {
|
||||
const cs = getComputedStyle(el);
|
||||
if (group === 'size') return nearestSize(el);
|
||||
if (group === 'color') return nearestColor(el);
|
||||
if (group === 'weight') return parseInt(cs.fontWeight, 10) >= 600 ? 'bold' : 'normal';
|
||||
if (group === 'style') return cs.fontStyle.indexOf('italic') >= 0 ? 'italic' : 'normal';
|
||||
if (group === 'align') { let x = cs.textAlign; if (x === 'start') x = 'left'; else if (x === 'end') x = 'right'; return ['left','center','right','justify'].indexOf(x) >= 0 ? x : 'left'; }
|
||||
return null;
|
||||
}
|
||||
const modal = document.getElementById('tag-modal');
|
||||
const tmTag = document.getElementById('tm-tag');
|
||||
const tmGuid = document.getElementById('tm-guid');
|
||||
@@ -185,7 +233,12 @@ const showTags = canTags && wantTags;
|
||||
if (groups.length) {
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'tm-styles';
|
||||
let anyEstimate = false;
|
||||
groups.forEach((group) => {
|
||||
const explicit = styles[group]; // valore salvato nel blocco
|
||||
const est = explicit ? null : effective(group, el); // altrimenti stima dall'aspetto reale
|
||||
const shown = explicit || est;
|
||||
if (est) anyEstimate = true;
|
||||
const field = document.createElement('label');
|
||||
field.className = 'tm-field';
|
||||
const cap = document.createElement('span');
|
||||
@@ -194,22 +247,32 @@ const showTags = canTags && wantTags;
|
||||
const sel = document.createElement('select');
|
||||
sel.dataset.group = group;
|
||||
sel.className = 'tm-style';
|
||||
if (est) sel.dataset.estimated = est; // marcata come stima: non fissata al salvataggio se invariata
|
||||
const none = document.createElement('option');
|
||||
none.value = ''; none.textContent = '— default —';
|
||||
none.value = ''; none.textContent = '— default (tema) —';
|
||||
sel.appendChild(none);
|
||||
STYLE_GROUPS[group].forEach((v) => {
|
||||
const o = document.createElement('option');
|
||||
o.value = v; o.textContent = valLabel(group, v);
|
||||
if (styles[group] === v) o.selected = true;
|
||||
o.value = v; o.textContent = valLabel(group, v) + (est === v ? ' (attuale)' : '');
|
||||
if (shown === v) o.selected = true;
|
||||
sel.appendChild(o);
|
||||
});
|
||||
// Evidenzia la combo se un valore è impostato.
|
||||
if (styles[group]) sel.classList.add('is-set');
|
||||
sel.addEventListener('change', () => sel.classList.toggle('is-set', !!sel.value));
|
||||
sel.classList.add(explicit ? 'is-set' : (est ? 'is-estimated' : ''));
|
||||
sel.addEventListener('change', () => {
|
||||
delete sel.dataset.estimated; // scelta manuale → diventa esplicita
|
||||
sel.classList.remove('is-estimated');
|
||||
sel.classList.toggle('is-set', !!sel.value);
|
||||
});
|
||||
field.appendChild(cap); field.appendChild(sel);
|
||||
grid.appendChild(field);
|
||||
});
|
||||
tmBody.appendChild(grid);
|
||||
if (anyEstimate) {
|
||||
const note = document.createElement('p');
|
||||
note.className = 'tm-note';
|
||||
note.textContent = 'I valori "(attuale)" riflettono l’aspetto dal tema: cambiane uno per fissarlo sul blocco.';
|
||||
tmBody.appendChild(note);
|
||||
}
|
||||
}
|
||||
}
|
||||
modal.hidden = false;
|
||||
@@ -227,10 +290,16 @@ const showTags = canTags && wantTags;
|
||||
return;
|
||||
}
|
||||
const value = document.getElementById('tm-value').value;
|
||||
const selects = document.querySelectorAll('.tm-style');
|
||||
const styles = {};
|
||||
document.querySelectorAll('.tm-style').forEach((s) => { if (s.value) styles[s.dataset.group] = s.value; });
|
||||
selects.forEach((s) => {
|
||||
const v = s.value;
|
||||
if (!v) return; // "default (tema)" → non impostato
|
||||
if (s.dataset.estimated && v === s.dataset.estimated) return; // stima non modificata → non fissare
|
||||
styles[s.dataset.group] = v;
|
||||
});
|
||||
const body = { value };
|
||||
if (Object.keys(styles).length) body.styles = styles;
|
||||
if (selects.length) body.styles = styles; // inviato sempre (anche {}) così l'azzeramento persiste
|
||||
const r = await fetch('/api/admin/content/' + encodeURIComponent(current.tag), {
|
||||
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user