fix(csp): wire display sliders and accent picker without inline on*
Lint & Test / test (push) Successful in 10s
Lint & Test / test (push) Successful in 10s
The display brightness/contrast sliders and the accent color picker rendered dynamic HTML with inline oninput/onchange/onclick attributes, which are blocked by the script-src 'self' CSP — so display settings were silently un-clickable from the WebUI. Replace the inline attributes with data-* markers, then attach proper event listeners after innerHTML (delegated on the container for the slider rows, direct for the accent dropdown).
This commit is contained in:
@@ -182,8 +182,7 @@ export async function loadDisplayMonitors() {
|
||||
<span class="display-slider-label" data-i18n="display.contrast">${t('display.contrast')}</span>
|
||||
<input type="range" class="display-slider display-contrast-slider"
|
||||
min="0" max="100" value="${contrastValue}"
|
||||
oninput="onDisplayContrastInput(${monitor.id}, this.value)"
|
||||
onchange="onDisplayContrastChange(${monitor.id}, this.value)">
|
||||
data-display-slider="contrast" data-monitor-id="${monitor.id}">
|
||||
<span class="display-slider-value" id="contrast-val-${monitor.id}">${contrastValue}%</span>
|
||||
</div>`;
|
||||
}
|
||||
@@ -296,8 +295,7 @@ export async function loadDisplayMonitors() {
|
||||
</svg>
|
||||
<span class="display-slider-label" data-i18n="display.brightness">${t('display.brightness')}</span>
|
||||
<input type="range" class="display-slider display-brightness-slider" min="0" max="100" value="${brightnessValue}" ${brightnessDisabled}
|
||||
oninput="onDisplayBrightnessInput(${monitor.id}, this.value)"
|
||||
onchange="onDisplayBrightnessChange(${monitor.id}, this.value)">
|
||||
data-display-slider="brightness" data-monitor-id="${monitor.id}">
|
||||
<span class="display-slider-value display-brightness-value" id="brightness-val-${monitor.id}">${brightnessValue}%</span>
|
||||
</div>
|
||||
${contrastRow}
|
||||
@@ -306,10 +304,15 @@ export async function loadDisplayMonitors() {
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
// Bind a single delegated click handler for the power buttons.
|
||||
// Avoids inline onclick="..." with interpolated monitor data.
|
||||
// Bind a single delegated click handler for the power buttons,
|
||||
// plus input/change handlers for the brightness & contrast sliders.
|
||||
// Avoids inline on* attributes (blocked by script-src 'self' CSP).
|
||||
container.removeEventListener('click', _onPowerButtonClick);
|
||||
container.addEventListener('click', _onPowerButtonClick);
|
||||
container.removeEventListener('input', _onDisplaySliderInput);
|
||||
container.addEventListener('input', _onDisplaySliderInput);
|
||||
container.removeEventListener('change', _onDisplaySliderChange);
|
||||
container.addEventListener('change', _onDisplaySliderChange);
|
||||
|
||||
// Enhance every tuning <select> with an IconSelect now that the
|
||||
// cards are in the DOM (IconSelect needs offsetParent + sibling).
|
||||
@@ -456,6 +459,30 @@ function _onPowerButtonClick(event) {
|
||||
if (Number.isFinite(id)) toggleDisplayPower(id);
|
||||
}
|
||||
|
||||
function _onDisplaySliderInput(event) {
|
||||
const el = event.target.closest('input[data-display-slider]');
|
||||
if (!el) return;
|
||||
const id = Number(el.dataset.monitorId);
|
||||
if (!Number.isFinite(id)) return;
|
||||
if (el.dataset.displaySlider === 'brightness') {
|
||||
onDisplayBrightnessInput(id, el.value);
|
||||
} else if (el.dataset.displaySlider === 'contrast') {
|
||||
onDisplayContrastInput(id, el.value);
|
||||
}
|
||||
}
|
||||
|
||||
function _onDisplaySliderChange(event) {
|
||||
const el = event.target.closest('input[data-display-slider]');
|
||||
if (!el) return;
|
||||
const id = Number(el.dataset.monitorId);
|
||||
if (!Number.isFinite(id)) return;
|
||||
if (el.dataset.displaySlider === 'brightness') {
|
||||
onDisplayBrightnessChange(id, el.value);
|
||||
} else if (el.dataset.displaySlider === 'contrast') {
|
||||
onDisplayContrastChange(id, el.value);
|
||||
}
|
||||
}
|
||||
|
||||
export async function toggleDisplayPower(monitorId) {
|
||||
const btn = document.getElementById(`power-btn-${monitorId}`);
|
||||
const isOn = btn && btn.classList.contains('on');
|
||||
|
||||
@@ -207,20 +207,39 @@ export function renderAccentSwatches() {
|
||||
const swatches = accentPresets.map(p =>
|
||||
`<div class="accent-swatch ${p.color === current ? 'active' : ''}"
|
||||
style="background: ${p.color}"
|
||||
onclick="selectAccentColor('${p.color}', '${p.hover}')"
|
||||
data-accent-color="${p.color}" data-accent-hover="${p.hover}"
|
||||
title="${p.name}"></div>`
|
||||
).join('');
|
||||
|
||||
const customRow = `
|
||||
<div class="accent-custom-row ${isCustom ? 'active' : ''}" onclick="document.getElementById('accentCustomInput').click()">
|
||||
<div class="accent-custom-row ${isCustom ? 'active' : ''}" data-accent-custom-row>
|
||||
<span class="accent-custom-swatch" style="background: ${isCustom ? current : '#888'}"></span>
|
||||
<span class="accent-custom-label">${t('accent.custom')}</span>
|
||||
<input type="color" id="accentCustomInput" value="${current}"
|
||||
onclick="event.stopPropagation()"
|
||||
onchange="selectAccentColor(this.value, lightenColor(this.value, 15))">
|
||||
<input type="color" id="accentCustomInput" value="${current}">
|
||||
</div>`;
|
||||
|
||||
dropdown.innerHTML = swatches + customRow;
|
||||
|
||||
// Wire CSP-safe handlers (script-src 'self' blocks inline on* attributes).
|
||||
dropdown.querySelectorAll('.accent-swatch[data-accent-color]').forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
selectAccentColor(el.dataset.accentColor, el.dataset.accentHover);
|
||||
});
|
||||
});
|
||||
const customRowEl = dropdown.querySelector('[data-accent-custom-row]');
|
||||
const customInput = dropdown.querySelector('#accentCustomInput');
|
||||
if (customRowEl && customInput) {
|
||||
customRowEl.addEventListener('click', (e) => {
|
||||
// The native color popup only opens from a user-initiated click on
|
||||
// the <input>. Forward clicks on the row to the input — except when
|
||||
// the input itself was the source (avoids re-entry).
|
||||
if (e.target !== customInput) customInput.click();
|
||||
});
|
||||
customInput.addEventListener('click', (e) => e.stopPropagation());
|
||||
customInput.addEventListener('change', () => {
|
||||
selectAccentColor(customInput.value, lightenColor(customInput.value, 15));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function selectAccentColor(color, hover) {
|
||||
|
||||
Reference in New Issue
Block a user