Single-container navigation service combining: - Dashy dashboard + SearXNG metasearch + history sync API + admin WebUI - Go services for history/admin APIs, nginx as unified entry - Browser extensions (Chrome MV3 / Firefox MV2) with 30s batched sync - Production guide: portal-nginx unified auth reverse proxy + webui4frpc/frp tunnel - Fully sanitized (no internal IPs/domains/credentials)
40 lines
1.4 KiB
JavaScript
40 lines
1.4 KiB
JavaScript
(() => {
|
|
'use strict';
|
|
|
|
function mountSearch() {
|
|
if (document.querySelector('.navi-search-bar')) return;
|
|
const form = document.createElement('form');
|
|
form.className = 'navi-search-bar';
|
|
form.method = 'GET';
|
|
form.action = '/search';
|
|
form.target = '_blank';
|
|
form.setAttribute('role', 'search');
|
|
|
|
const icon = document.createElement('span');
|
|
icon.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>';
|
|
icon.setAttribute('aria-hidden', 'true');
|
|
|
|
const input = document.createElement('input');
|
|
input.type = 'search';
|
|
input.name = 'q';
|
|
input.placeholder = '搜索…';
|
|
input.autocomplete = 'off';
|
|
input.setAttribute('aria-label', '搜索');
|
|
|
|
const button = document.createElement('button');
|
|
button.type = 'submit';
|
|
button.textContent = '→';
|
|
button.title = '搜索';
|
|
|
|
form.append(icon, input, button);
|
|
document.body.appendChild(form);
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', mountSearch, { once: true });
|
|
} else {
|
|
mountSearch();
|
|
}
|
|
new MutationObserver(mountSearch).observe(document.documentElement, { childList: true, subtree: true });
|
|
})();
|