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:
parent
ba06582218
commit
7ea50d3123
3 changed files with 60 additions and 3 deletions
|
|
@ -7,7 +7,8 @@ var DEFAULT_PREFS = {
|
||||||
theme: 'system',
|
theme: 'system',
|
||||||
engines: ALL_ENGINES.slice(),
|
engines: ALL_ENGINES.slice(),
|
||||||
safeSearch: 'moderate',
|
safeSearch: 'moderate',
|
||||||
format: 'html'
|
format: 'html',
|
||||||
|
favicon: 'none' // 'none' | 'google' | 'duckduckgo'
|
||||||
};
|
};
|
||||||
|
|
||||||
var STORAGE_KEY = 'kafka_prefs';
|
var STORAGE_KEY = 'kafka_prefs';
|
||||||
|
|
@ -22,7 +23,8 @@ function loadPrefs() {
|
||||||
theme: parsed.theme || DEFAULT_PREFS.theme,
|
theme: parsed.theme || DEFAULT_PREFS.theme,
|
||||||
engines: parsed.engines || DEFAULT_PREFS.engines.slice(),
|
engines: parsed.engines || DEFAULT_PREFS.engines.slice(),
|
||||||
safeSearch: parsed.safeSearch || DEFAULT_PREFS.safeSearch,
|
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) {
|
} catch (e) {
|
||||||
prefs = DEFAULT_PREFS;
|
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) {
|
function syncEngineInput(prefs) {
|
||||||
var input = document.getElementById('engines-input');
|
var input = document.getElementById('engines-input');
|
||||||
if (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 =
|
body.innerHTML =
|
||||||
'<div class="settings-section">' +
|
'<div class="settings-section">' +
|
||||||
'<div class="settings-section-title">Appearance</div>' +
|
'<div class="settings-section-title">Appearance</div>' +
|
||||||
|
|
@ -124,6 +151,10 @@ function renderPanel(prefs) {
|
||||||
'<label for="pref-format">Default format</label>' +
|
'<label for="pref-format">Default format</label>' +
|
||||||
'<select id="pref-format">' + fmtOptionsHtml + '</select>' +
|
'<select id="pref-format">' + fmtOptionsHtml + '</select>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
|
'<div class="setting-row">' +
|
||||||
|
'<label for="pref-favicon">Favicon service</label>' +
|
||||||
|
'<select id="pref-favicon">' + faviconOptions + '</select>' +
|
||||||
|
'</div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
|
|
||||||
// Theme buttons
|
// Theme buttons
|
||||||
|
|
@ -162,6 +193,7 @@ function renderPanel(prefs) {
|
||||||
function initSettings() {
|
function initSettings() {
|
||||||
var prefs = loadPrefs();
|
var prefs = loadPrefs();
|
||||||
applyTheme(prefs.theme);
|
applyTheme(prefs.theme);
|
||||||
|
applyFavicon(prefs.favicon);
|
||||||
syncEngineInput(prefs);
|
syncEngineInput(prefs);
|
||||||
renderPanel(prefs);
|
renderPanel(prefs);
|
||||||
|
|
||||||
|
|
@ -257,6 +289,9 @@ function initPreferences() {
|
||||||
// Load saved preferences
|
// Load saved preferences
|
||||||
var prefs = loadPrefs();
|
var prefs = loadPrefs();
|
||||||
|
|
||||||
|
// Apply favicon settings immediately on preferences page
|
||||||
|
applyFavicon(prefs.favicon);
|
||||||
|
|
||||||
// Theme
|
// Theme
|
||||||
var themeEl = document.getElementById('pref-theme');
|
var themeEl = document.getElementById('pref-theme');
|
||||||
if (themeEl) {
|
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
|
// Show first section by default
|
||||||
showSection('search');
|
showSection('search');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,17 @@
|
||||||
<option value="2">Strict</option>
|
<option value="2">Strict</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pref-row">
|
||||||
|
<div class="pref-row-info">
|
||||||
|
<label>Favicon Service</label>
|
||||||
|
<p class="pref-desc">Fetch favicons for result URLs. "None" is most private.</p>
|
||||||
|
</div>
|
||||||
|
<select name="favicon" id="pref-favicon">
|
||||||
|
<option value="none" selected>None</option>
|
||||||
|
<option value="google">Google</option>
|
||||||
|
<option value="duckduckgo">DuckDuckGo</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="pref-section">
|
<section class="pref-section">
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.SafeTitle}}</a>
|
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.SafeTitle}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="result_url">
|
<div class="result_url">
|
||||||
<img class="result-favicon" src="https://www.google.com/s2/favicons?domain={{.URL | urlquery}}&sz=32" alt="" loading="lazy" onerror="this.style.display='none'">
|
<img class="result-favicon" data-domain="{{.URL}}" src="" alt="" loading="lazy" style="display:none">
|
||||||
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a>
|
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a>
|
||||||
<span class="engine-badge" data-engine="{{.Engine}}">{{.Engine}}</span>
|
<span class="engine-badge" data-engine="{{.Engine}}">{{.Engine}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue