feat(ui): make favicons user-configurable, off by default

- Add favicon service preference: None (default), Google, DuckDuckGo
- result_item.html: remove hardcoded Google favicon src, defer to JS
- applyFavicon() reads data-domain attr and sets src or display:none
- Privacy-by-default: users must explicitly opt in to any favicon service
- Add favicon selector to both the settings panel and preferences page
This commit is contained in:
Franz Kafka 2026-03-23 14:22:24 +00:00
parent ba06582218
commit 7ea50d3123
3 changed files with 60 additions and 3 deletions

View file

@ -7,7 +7,8 @@ var DEFAULT_PREFS = {
theme: 'system',
engines: ALL_ENGINES.slice(),
safeSearch: 'moderate',
format: 'html'
format: 'html',
favicon: 'none' // 'none' | 'google' | 'duckduckgo'
};
var STORAGE_KEY = 'kafka_prefs';
@ -22,7 +23,8 @@ function loadPrefs() {
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
format: parsed.format || DEFAULT_PREFS.format,
favicon: parsed.favicon || DEFAULT_PREFS.favicon
};
} catch (e) {
prefs = DEFAULT_PREFS;
@ -43,6 +45,24 @@ function applyTheme(theme) {
}
}
function applyFavicon(service) {
var faviconMap = {
google: function(domain) { return 'https://www.google.com/s2/favicons?domain=' + encodeURIComponent(domain) + '&sz=32'; },
duckduckgo: function(domain) { return 'https://icons.duckduckgo.com/ip3/' + encodeURIComponent(domain) + '.ico'; }
};
var imgs = document.querySelectorAll('.result-favicon');
imgs.forEach(function(img) {
var domain = img.getAttribute('data-domain');
if (!domain) return;
if (service === 'none') {
img.style.display = 'none';
} else {
img.style.display = '';
img.src = faviconMap[service](domain);
}
});
}
function syncEngineInput(prefs) {
var input = document.getElementById('engines-input');
if (input) {
@ -104,6 +124,13 @@ function renderPanel(prefs) {
});
var faviconOptions = '';
['none', 'google', 'duckduckgo'].forEach(function(src) {
var labels = { none: 'None', google: 'Google', duckduckgo: 'DuckDuckGo' };
var selected = prefs.favicon === src ? ' selected' : '';
faviconOptions += '<option value="' + src + '"' + selected + '>' + labels[src] + '</option>';
});
body.innerHTML =
'<div class="settings-section">' +
'<div class="settings-section-title">Appearance</div>' +
@ -124,6 +151,10 @@ function renderPanel(prefs) {
'<label for="pref-format">Default format</label>' +
'<select id="pref-format">' + fmtOptionsHtml + '</select>' +
'</div>' +
'<div class="setting-row">' +
'<label for="pref-favicon">Favicon service</label>' +
'<select id="pref-favicon">' + faviconOptions + '</select>' +
'</div>' +
'</div>';
// Theme buttons
@ -162,6 +193,7 @@ function renderPanel(prefs) {
function initSettings() {
var prefs = loadPrefs();
applyTheme(prefs.theme);
applyFavicon(prefs.favicon);
syncEngineInput(prefs);
renderPanel(prefs);
@ -257,6 +289,9 @@ function initPreferences() {
// Load saved preferences
var prefs = loadPrefs();
// Apply favicon settings immediately on preferences page
applyFavicon(prefs.favicon);
// Theme
var themeEl = document.getElementById('pref-theme');
if (themeEl) {
@ -288,6 +323,17 @@ function initPreferences() {
});
}
// Favicon service (if exists on page)
var faviconEl = document.getElementById('pref-favicon');
if (faviconEl) {
faviconEl.value = prefs.favicon || 'none';
faviconEl.addEventListener('change', function() {
prefs.favicon = faviconEl.value;
savePrefs(prefs);
applyFavicon(prefs.favicon);
});
}
// Show first section by default
showSection('search');
}