editor inline: combo mostrano il valore reale stimato dall'aspetto
Se un blocco non ha uno stile esplicito, le combo pre-selezionano il preset più vicino all'aspetto effettivo (getComputedStyle): size dal rapporto col genitore, color per match RGB, weight/style/align diretti. Marcato "(attuale)" con bordo tratteggiato. Al salvataggio le stime non modificate non vengono fissate, così il tema resta intatto; solo le scelte manuali vengono scritte. Gli styles ora sono inviati sempre per permettere l'azzeramento a "default (tema)". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+67
-8
@@ -103,6 +103,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 +143,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 +223,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 +237,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 +280,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