- Replace document.body.innerHTML with panel.querySelector('.settings-popover-body').innerHTML
- Use theme buttons (.theme-btn) with icons instead of radio buttons
- Use .engine-toggle class for engine checkboxes in 2-column grid
- Include settings-notice paragraph for engine changes
- Use dropdowns for safe search and format with proper ids
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
271 lines
8.3 KiB
JavaScript
271 lines
8.3 KiB
JavaScript
var ALL_ENGINES = [
|
|
'wikipedia', 'arxiv', 'crossref', 'braveapi',
|
|
'qwant', 'duckduckgo', 'github', 'reddit', 'bing'
|
|
];
|
|
|
|
var DEFAULT_PREFS = {
|
|
theme: 'system',
|
|
engines: ALL_ENGINES.slice(),
|
|
safeSearch: 'moderate',
|
|
format: 'html'
|
|
};
|
|
|
|
var STORAGE_KEY = 'kafka_prefs';
|
|
|
|
function loadPrefs() {
|
|
var stored = localStorage.getItem(STORAGE_KEY);
|
|
var prefs = DEFAULT_PREFS;
|
|
if (stored) {
|
|
try {
|
|
var parsed = JSON.parse(stored);
|
|
prefs = {
|
|
theme: parsed.theme || DEFAULT_PREFS.theme,
|
|
engines: parsed.engines || DEFAULT_PREFS.engines.slice(),
|
|
safeSearch: parsed.safeSearch || DEFAULT_PREFS.safeSearch,
|
|
format: parsed.format || DEFAULT_PREFS.format
|
|
};
|
|
} catch (e) {
|
|
prefs = DEFAULT_PREFS;
|
|
}
|
|
}
|
|
return prefs;
|
|
}
|
|
|
|
function savePrefs(prefs) {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));
|
|
}
|
|
|
|
function applyTheme(theme) {
|
|
if (theme === 'system') {
|
|
document.documentElement.removeAttribute('data-theme');
|
|
} else {
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
}
|
|
}
|
|
|
|
function syncEngineInput(prefs) {
|
|
var input = document.getElementById('engines-input');
|
|
if (input) {
|
|
input.value = prefs.engines.join(',');
|
|
}
|
|
}
|
|
|
|
function closePanel() {
|
|
var popover = document.getElementById('settings-popover');
|
|
var trigger = document.getElementById('settings-trigger');
|
|
if (popover) {
|
|
popover.setAttribute('data-open', 'false');
|
|
}
|
|
if (trigger) {
|
|
trigger.setAttribute('aria-expanded', 'false');
|
|
}
|
|
if (trigger) {
|
|
trigger.focus();
|
|
}
|
|
}
|
|
|
|
function openPanel() {
|
|
var popover = document.getElementById('settings-popover');
|
|
var trigger = document.getElementById('settings-trigger');
|
|
if (popover) {
|
|
popover.setAttribute('data-open', 'true');
|
|
}
|
|
if (trigger) {
|
|
trigger.setAttribute('aria-expanded', 'true');
|
|
}
|
|
var firstFocusable = popover ? popover.querySelector('button, input, select, textarea, a[href], [tabindex]:not([tabindex="-1"])') : null;
|
|
if (firstFocusable) {
|
|
firstFocusable.focus();
|
|
}
|
|
}
|
|
|
|
function escapeHtml(str) {
|
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
}
|
|
|
|
function renderPanel(prefs) {
|
|
var panel = document.getElementById('settings-popover');
|
|
if (!panel) return;
|
|
var body = panel.querySelector('.settings-popover-body');
|
|
if (!body) return;
|
|
|
|
var themeBtns = '';
|
|
['light', 'dark', 'system'].forEach(function(t) {
|
|
var icons = { light: '\u2600', dark: '\u263D', system: '\u2318' };
|
|
var labels = { light: 'Light', dark: 'Dark', system: 'System' };
|
|
var active = prefs.theme === t ? ' active' : '';
|
|
themeBtns += '<button class="theme-btn' + active + '" data-theme="' + t + '">' + icons[t] + ' ' + labels[t] + '</button>';
|
|
});
|
|
|
|
var engineToggles = '';
|
|
ALL_ENGINES.forEach(function(name) {
|
|
var checked = prefs.engines.indexOf(name) !== -1 ? ' checked' : '';
|
|
engineToggles += '<label class="engine-toggle"><input type="checkbox" value="' + escapeHtml(name) + '"' + checked + '><span>' + escapeHtml(name) + '</span></label>';
|
|
});
|
|
|
|
var ssOptions = [
|
|
{ val: 'moderate', label: 'Moderate' },
|
|
{ val: 'strict', label: 'Strict' },
|
|
{ val: 'off', label: 'Off' }
|
|
];
|
|
var fmtOptions = [
|
|
{ val: 'html', label: 'HTML' },
|
|
{ val: 'json', label: 'JSON' },
|
|
{ val: 'csv', label: 'CSV' },
|
|
{ val: 'rss', label: 'RSS' }
|
|
];
|
|
var ssOptionsHtml = '';
|
|
var fmtOptionsHtml = '';
|
|
ssOptions.forEach(function(o) {
|
|
var sel = prefs.safeSearch === o.val ? ' selected' : '';
|
|
ssOptionsHtml += '<option value="' + o.val + '"' + sel + '>' + o.label + '</option>';
|
|
});
|
|
fmtOptions.forEach(function(o) {
|
|
var sel = prefs.format === o.val ? ' selected' : '';
|
|
fmtOptionsHtml += '<option value="' + o.val + '"' + sel + '>' + o.label + '</option>';
|
|
});
|
|
|
|
body.innerHTML =
|
|
'<div class="settings-section">' +
|
|
'<div class="settings-section-title">Appearance</div>' +
|
|
'<div class="theme-buttons">' + themeBtns + '</div>' +
|
|
'</div>' +
|
|
'<div class="settings-section">' +
|
|
'<div class="settings-section-title">Engines</div>' +
|
|
'<div class="engine-grid">' + engineToggles + '</div>' +
|
|
'<p class="settings-notice">Engine changes apply to your next search.</p>' +
|
|
'</div>' +
|
|
'<div class="settings-section">' +
|
|
'<div class="settings-section-title">Search Defaults</div>' +
|
|
'<div class="setting-row">' +
|
|
'<label for="pref-safesearch">Safe search</label>' +
|
|
'<select id="pref-safesearch">' + ssOptionsHtml + '</select>' +
|
|
'</div>' +
|
|
'<div class="setting-row">' +
|
|
'<label for="pref-format">Default format</label>' +
|
|
'<select id="pref-format">' + fmtOptionsHtml + '</select>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
// Theme buttons
|
|
var themeBtnEls = panel.querySelectorAll('.theme-btn');
|
|
for (var i = 0; i < themeBtnEls.length; i++) {
|
|
themeBtnEls[i].addEventListener('click', (function(btn) {
|
|
return function() {
|
|
prefs.theme = btn.getAttribute('data-theme');
|
|
savePrefs(prefs);
|
|
applyTheme(prefs.theme);
|
|
syncEngineInput(prefs);
|
|
renderPanel(prefs);
|
|
};
|
|
})(themeBtnEls[i]));
|
|
}
|
|
|
|
// Engine checkboxes
|
|
var checkboxes = panel.querySelectorAll('.engine-toggle input[type="checkbox"]');
|
|
for (var j = 0; j < checkboxes.length; j++) {
|
|
checkboxes[j].addEventListener('change', (function(cb) {
|
|
return function() {
|
|
var checked = Array.prototype.slice.call(panel.querySelectorAll('.engine-toggle input[type="checkbox"]:checked')).map(function(el) { return el.value; });
|
|
if (checked.length === 0) { renderPanel(prefs); return; }
|
|
prefs.engines = checked;
|
|
savePrefs(prefs);
|
|
syncEngineInput(prefs);
|
|
};
|
|
})(checkboxes[j]));
|
|
}
|
|
|
|
// Safe search
|
|
var ssEl = panel.querySelector('#pref-safesearch');
|
|
if (ssEl) {
|
|
ssEl.addEventListener('change', function() {
|
|
prefs.safeSearch = ssEl.value;
|
|
savePrefs(prefs);
|
|
});
|
|
}
|
|
|
|
// Format
|
|
var fmtEl = panel.querySelector('#pref-format');
|
|
if (fmtEl) {
|
|
fmtEl.addEventListener('change', function() {
|
|
prefs.format = fmtEl.value;
|
|
savePrefs(prefs);
|
|
});
|
|
}
|
|
|
|
// Close button
|
|
var closeBtn = panel.querySelector('.settings-popover-close');
|
|
if (closeBtn) closeBtn.addEventListener('click', closePanel);
|
|
}
|
|
|
|
function initSettings() {
|
|
var prefs = loadPrefs();
|
|
applyTheme(prefs.theme);
|
|
syncEngineInput(prefs);
|
|
renderPanel(prefs);
|
|
|
|
var trigger = document.getElementById('settings-trigger');
|
|
var triggerMobile = document.getElementById('settings-trigger-mobile');
|
|
|
|
function togglePanel() {
|
|
var popover = document.getElementById('settings-popover');
|
|
if (popover && popover.getAttribute('data-open') === 'true') {
|
|
closePanel();
|
|
} else {
|
|
openPanel();
|
|
}
|
|
}
|
|
|
|
if (trigger) {
|
|
trigger.addEventListener('click', togglePanel);
|
|
}
|
|
if (triggerMobile) {
|
|
triggerMobile.addEventListener('click', togglePanel);
|
|
}
|
|
}
|
|
|
|
// Escape key handler
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
var popover = document.getElementById('settings-popover');
|
|
if (popover && popover.getAttribute('data-open') === 'true') {
|
|
closePanel();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Click outside handler
|
|
document.addEventListener('click', function(e) {
|
|
var popover = document.getElementById('settings-popover');
|
|
var trigger = document.getElementById('settings-trigger');
|
|
if (popover && popover.getAttribute('data-open') === 'true') {
|
|
if (!popover.contains(e.target) && (!trigger || !trigger.contains(e.target))) {
|
|
closePanel();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Focus trap
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Tab') {
|
|
var popover = document.getElementById('settings-popover');
|
|
if (popover && popover.getAttribute('data-open') === 'true') {
|
|
var focusableElements = popover.querySelectorAll('button, input, select, textarea, a[href], [tabindex]:not([tabindex="-1"])');
|
|
var firstEl = focusableElements[0];
|
|
var lastEl = focusableElements[focusableElements.length - 1];
|
|
if (e.shiftKey && document.activeElement === firstEl) {
|
|
e.preventDefault();
|
|
lastEl.focus();
|
|
} else if (!e.shiftKey && document.activeElement === lastEl) {
|
|
e.preventDefault();
|
|
firstEl.focus();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', initSettings);
|
|
} else {
|
|
initSettings();
|
|
}
|