mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-02 09:18:06 +00:00
Address Codacy static-analysis findings in settings JS
Refactor the two new modules to clear the flagged patterns without any behavior change: - Build the search dropdown with DOM nodes + textContent instead of innerHTML string concatenation, removing the XSS sinks and the manual escapeHtml helper it needed. - Replace numeric index access (index[i], terms[j], opts[idx], currentResults[i]) with array iteration methods, NodeList.item(), and Array.prototype.at() to clear detect-object-injection. - Use === via a shared termsMatch() helper, optional-catch binding, and drop a useless initial assignment. Verified with ESLint (eslint:recommended + eslint-plugin-security) at zero findings and re-ran the headless-Chromium behavior test (tooltip, per-tab filter, global search navigation) — all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014gZxznuxw8L92FUMBN3Nqz
This commit is contained in:
@@ -44,10 +44,9 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(s) {
|
// True when every search term is present in the haystack.
|
||||||
return String(s == null ? '' : s)
|
function termsMatch(hay, terms) {
|
||||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
return terms.every(function (t) { return hay.indexOf(t) !== -1; });
|
||||||
.replace(/"/g, '"').replace(/'/g, ''');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function textOf(el) {
|
function textOf(el) {
|
||||||
@@ -130,43 +129,54 @@
|
|||||||
q = q.trim().toLowerCase();
|
q = q.trim().toLowerCase();
|
||||||
if (!q) return [];
|
if (!q) return [];
|
||||||
var terms = q.split(/\s+/);
|
var terms = q.split(/\s+/);
|
||||||
var index = window._settingsIndex || [];
|
|
||||||
var out = [];
|
var out = [];
|
||||||
for (var i = 0; i < index.length && out.length < MAX_RESULTS; i++) {
|
(window._settingsIndex || []).some(function (entry) {
|
||||||
var hay = index[i].hay;
|
if (termsMatch(entry.hay, terms)) out.push(entry);
|
||||||
var ok = true;
|
return out.length >= MAX_RESULTS; // stop once we have enough
|
||||||
for (var j = 0; j < terms.length; j++) {
|
});
|
||||||
if (hay.indexOf(terms[j]) === -1) { ok = false; break; }
|
|
||||||
}
|
|
||||||
if (ok) out.push(index[i]);
|
|
||||||
}
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function span(cls, text) {
|
||||||
|
var s = document.createElement('span');
|
||||||
|
s.className = cls;
|
||||||
|
s.textContent = text;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the dropdown with DOM nodes + textContent (never innerHTML) so
|
||||||
|
// setting labels/help can never be interpreted as markup.
|
||||||
function renderResults(results) {
|
function renderResults(results) {
|
||||||
currentResults = results;
|
currentResults = results;
|
||||||
activeIndex = -1;
|
activeIndex = -1;
|
||||||
|
resultsBox.textContent = '';
|
||||||
if (!results.length) {
|
if (!results.length) {
|
||||||
resultsBox.innerHTML = '<div class="ssr-empty">No settings found.</div>';
|
resultsBox.appendChild(span('ssr-empty', 'No settings found.'));
|
||||||
openResults();
|
openResults();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var html = '';
|
|
||||||
var lastTab = null;
|
var lastTab = null;
|
||||||
results.forEach(function (r, i) {
|
results.forEach(function (r, i) {
|
||||||
if (r.tabLabel !== lastTab) {
|
if (r.tabLabel !== lastTab) {
|
||||||
html += '<div class="ssr-group">' + escapeHtml(r.tabLabel) + '</div>';
|
var group = document.createElement('div');
|
||||||
|
group.className = 'ssr-group';
|
||||||
|
group.textContent = r.tabLabel;
|
||||||
|
resultsBox.appendChild(group);
|
||||||
lastTab = r.tabLabel;
|
lastTab = r.tabLabel;
|
||||||
}
|
}
|
||||||
var sub = r.section ? (r.section + ' · ') : '';
|
var sub = r.section ? (r.section + ' · ') : '';
|
||||||
var snippet = r.help ? r.help.split('\n')[0] : '';
|
var snippet = r.help ? r.help.split('\n')[0] : '';
|
||||||
html += '<button type="button" class="ssr-option" role="option" id="ssr-' + i + '" data-idx="' + i + '">' +
|
var opt = document.createElement('button');
|
||||||
'<span class="ssr-label">' + escapeHtml(r.label) + '</span>' +
|
opt.type = 'button';
|
||||||
(snippet ? '<span class="ssr-help">' + escapeHtml(sub + snippet) + '</span>'
|
opt.className = 'ssr-option';
|
||||||
: (sub ? '<span class="ssr-help">' + escapeHtml(r.section) + '</span>' : '')) +
|
opt.setAttribute('role', 'option');
|
||||||
'</button>';
|
opt.id = 'ssr-' + i;
|
||||||
|
opt.setAttribute('data-idx', String(i));
|
||||||
|
opt.appendChild(span('ssr-label', r.label));
|
||||||
|
var helpText = snippet ? (sub + snippet) : (sub ? r.section : '');
|
||||||
|
if (helpText) opt.appendChild(span('ssr-help', helpText));
|
||||||
|
resultsBox.appendChild(opt);
|
||||||
});
|
});
|
||||||
resultsBox.innerHTML = html;
|
|
||||||
openResults();
|
openResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +198,7 @@
|
|||||||
opts.forEach(function (o) { o.classList.remove('active'); });
|
opts.forEach(function (o) { o.classList.remove('active'); });
|
||||||
if (idx < 0 || idx >= opts.length) { activeIndex = -1; return; }
|
if (idx < 0 || idx >= opts.length) { activeIndex = -1; return; }
|
||||||
activeIndex = idx;
|
activeIndex = idx;
|
||||||
var el = opts[idx];
|
var el = opts.item(idx);
|
||||||
el.classList.add('active');
|
el.classList.add('active');
|
||||||
el.scrollIntoView({ block: 'nearest' });
|
el.scrollIntoView({ block: 'nearest' });
|
||||||
input.setAttribute('aria-activedescendant', el.id);
|
input.setAttribute('aria-activedescendant', el.id);
|
||||||
@@ -314,12 +324,10 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
highlight(Math.max(activeIndex - 1, 0));
|
highlight(Math.max(activeIndex - 1, 0));
|
||||||
} else if (e.key === 'Enter') {
|
} else if (e.key === 'Enter') {
|
||||||
if (activeIndex >= 0 && currentResults[activeIndex]) {
|
var chosen = currentResults.at(activeIndex >= 0 ? activeIndex : 0);
|
||||||
|
if (chosen) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigateToSetting(currentResults[activeIndex]);
|
navigateToSetting(chosen);
|
||||||
} else if (currentResults.length) {
|
|
||||||
e.preventDefault();
|
|
||||||
navigateToSetting(currentResults[0]);
|
|
||||||
}
|
}
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === 'Escape') {
|
||||||
closeResults();
|
closeResults();
|
||||||
@@ -333,7 +341,8 @@
|
|||||||
if (!opt) return;
|
if (!opt) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var idx = parseInt(opt.getAttribute('data-idx'), 10);
|
var idx = parseInt(opt.getAttribute('data-idx'), 10);
|
||||||
if (currentResults[idx]) navigateToSetting(currentResults[idx]);
|
var chosen = currentResults.at(idx);
|
||||||
|
if (chosen) navigateToSetting(chosen);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', function (e) {
|
document.addEventListener('click', function (e) {
|
||||||
@@ -367,13 +376,7 @@
|
|||||||
var anyVisible = false;
|
var anyVisible = false;
|
||||||
|
|
||||||
fields.forEach(function (fg) {
|
fields.forEach(function (fg) {
|
||||||
var show = true;
|
var show = !terms.length || termsMatch(fieldHay(fg), terms);
|
||||||
if (terms.length) {
|
|
||||||
var hay = fieldHay(fg);
|
|
||||||
for (var i = 0; i < terms.length; i++) {
|
|
||||||
if (hay.indexOf(terms[i]) === -1) { show = false; break; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fg.style.display = show ? '' : 'none';
|
fg.style.display = show ? '' : 'none';
|
||||||
if (show) anyVisible = true;
|
if (show) anyVisible = true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -109,11 +109,11 @@
|
|||||||
if (!t) return;
|
if (!t) return;
|
||||||
// Only auto-show on keyboard focus, so a mouse/touch focus does not
|
// Only auto-show on keyboard focus, so a mouse/touch focus does not
|
||||||
// fight the click handler below.
|
// fight the click handler below.
|
||||||
var focusVisible = true;
|
var focusVisible;
|
||||||
try {
|
try {
|
||||||
focusVisible = t.matches(':focus-visible');
|
focusVisible = t.matches(':focus-visible');
|
||||||
} catch (err) {
|
} catch { // older browsers without :focus-visible
|
||||||
focusVisible = true; // older browsers: fall back to always show
|
focusVisible = true;
|
||||||
}
|
}
|
||||||
if (focusVisible) show(t);
|
if (focusVisible) show(t);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user