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; }
|
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: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-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;
|
.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; }
|
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; }
|
.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' },
|
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;
|
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 modal = document.getElementById('tag-modal');
|
||||||
const tmTag = document.getElementById('tm-tag');
|
const tmTag = document.getElementById('tm-tag');
|
||||||
const tmGuid = document.getElementById('tm-guid');
|
const tmGuid = document.getElementById('tm-guid');
|
||||||
@@ -185,7 +223,12 @@ const showTags = canTags && wantTags;
|
|||||||
if (groups.length) {
|
if (groups.length) {
|
||||||
const grid = document.createElement('div');
|
const grid = document.createElement('div');
|
||||||
grid.className = 'tm-styles';
|
grid.className = 'tm-styles';
|
||||||
|
let anyEstimate = false;
|
||||||
groups.forEach((group) => {
|
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');
|
const field = document.createElement('label');
|
||||||
field.className = 'tm-field';
|
field.className = 'tm-field';
|
||||||
const cap = document.createElement('span');
|
const cap = document.createElement('span');
|
||||||
@@ -194,22 +237,32 @@ const showTags = canTags && wantTags;
|
|||||||
const sel = document.createElement('select');
|
const sel = document.createElement('select');
|
||||||
sel.dataset.group = group;
|
sel.dataset.group = group;
|
||||||
sel.className = 'tm-style';
|
sel.className = 'tm-style';
|
||||||
|
if (est) sel.dataset.estimated = est; // marcata come stima: non fissata al salvataggio se invariata
|
||||||
const none = document.createElement('option');
|
const none = document.createElement('option');
|
||||||
none.value = ''; none.textContent = '— default —';
|
none.value = ''; none.textContent = '— default (tema) —';
|
||||||
sel.appendChild(none);
|
sel.appendChild(none);
|
||||||
STYLE_GROUPS[group].forEach((v) => {
|
STYLE_GROUPS[group].forEach((v) => {
|
||||||
const o = document.createElement('option');
|
const o = document.createElement('option');
|
||||||
o.value = v; o.textContent = valLabel(group, v);
|
o.value = v; o.textContent = valLabel(group, v) + (est === v ? ' (attuale)' : '');
|
||||||
if (styles[group] === v) o.selected = true;
|
if (shown === v) o.selected = true;
|
||||||
sel.appendChild(o);
|
sel.appendChild(o);
|
||||||
});
|
});
|
||||||
// Evidenzia la combo se un valore è impostato.
|
sel.classList.add(explicit ? 'is-set' : (est ? 'is-estimated' : ''));
|
||||||
if (styles[group]) sel.classList.add('is-set');
|
sel.addEventListener('change', () => {
|
||||||
sel.addEventListener('change', () => sel.classList.toggle('is-set', !!sel.value));
|
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);
|
field.appendChild(cap); field.appendChild(sel);
|
||||||
grid.appendChild(field);
|
grid.appendChild(field);
|
||||||
});
|
});
|
||||||
tmBody.appendChild(grid);
|
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;
|
modal.hidden = false;
|
||||||
@@ -227,10 +280,16 @@ const showTags = canTags && wantTags;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const value = document.getElementById('tm-value').value;
|
const value = document.getElementById('tm-value').value;
|
||||||
|
const selects = document.querySelectorAll('.tm-style');
|
||||||
const styles = {};
|
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 };
|
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), {
|
const r = await fetch('/api/admin/content/' + encodeURIComponent(current.tag), {
|
||||||
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user