kafka/internal/views/static/js/settings.js

295 lines
8.8 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
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>';
});
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]));
}
// 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();
}
// Preferences page navigation
function initPreferences() {
var nav = document.getElementById('preferences-nav');
if (!nav) return;
var sections = document.querySelectorAll('.pref-section');
var navItems = nav.querySelectorAll('.preferences-nav-item');
function showSection(id) {
sections.forEach(function(sec) {
sec.style.display = sec.id === 'section-' + id ? 'block' : 'none';
});
navItems.forEach(function(item) {
item.classList.toggle('active', item.getAttribute('data-section') === id);
});
}
navItems.forEach(function(item) {
item.addEventListener('click', function() {
showSection(item.getAttribute('data-section'));
});
});
// Load saved preferences
var prefs = loadPrefs();
// Theme
var themeEl = document.getElementById('pref-theme');
if (themeEl) {
themeEl.value = prefs.theme || 'system';
themeEl.addEventListener('change', function() {
prefs.theme = themeEl.value;
savePrefs(prefs);
applyTheme(prefs.theme);
});
}
// Safe search
var ssEl = document.getElementById('pref-safesearch');
if (ssEl) {
ssEl.value = prefs.safeSearch || 'moderate';
ssEl.addEventListener('change', function() {
prefs.safeSearch = ssEl.value;
savePrefs(prefs);
});
}
// Format (if exists on page)
var fmtEl = document.getElementById('pref-format');
if (fmtEl) {
fmtEl.value = prefs.format || 'html';
fmtEl.addEventListener('change', function() {
prefs.format = fmtEl.value;
savePrefs(prefs);
});
}
// Show first section by default
showSection('search');
}
document.addEventListener('DOMContentLoaded', initPreferences);