Theseus: per-engine symbols + light-theme cascade fix
- Each search engine shows a symbol in the toolbar dropdown, the placeholder, and Settings (🦆 DuckDuckGo, 🦁 Brave, 🔵 Google, 🔎 Bing, 🛡️ Startpage, 🔴 Yandex); custom engines take an optional symbol (defaults to 🔍). - Fix: light-theme @media blocks for settings/popover/home were placed before the base rules and lost the cascade (sidebar stayed dark) — moved them last.
This commit is contained in:
parent
2757cd8c64
commit
ad34ad3bbe
3 changed files with 47 additions and 21 deletions
|
|
@ -158,11 +158,11 @@
|
||||||
function fillEngines(d) {
|
function fillEngines(d) {
|
||||||
engineCurrent = d.current;
|
engineCurrent = d.current;
|
||||||
const sel = $("engine");
|
const sel = $("engine");
|
||||||
sel.innerHTML = d.engines.map((e) => `<option value="${e.id}">${e.name.replace(/</g,"<")}</option>`).join("")
|
sel.innerHTML = d.engines.map((e) => `<option value="${e.id}">${(e.sym ? e.sym + " " : "")}${e.name.replace(/</g,"<")}</option>`).join("")
|
||||||
+ `<option disabled>──────────</option><option value="__manage">+ Add / edit engines…</option>`;
|
+ `<option disabled>──────────</option><option value="__manage">+ Add / edit engines…</option>`;
|
||||||
sel.value = d.current;
|
sel.value = d.current;
|
||||||
const cur = d.engines.find((e) => e.id === d.current);
|
const cur = d.engines.find((e) => e.id === d.current);
|
||||||
$("search").placeholder = cur ? "Search with " + cur.name : "Search";
|
$("search").placeholder = cur ? "Search with " + (cur.sym ? cur.sym + " " : "") + cur.name : "Search";
|
||||||
}
|
}
|
||||||
$("engine").onchange = () => {
|
$("engine").onchange = () => {
|
||||||
const v = $("engine").value;
|
const v = $("engine").value;
|
||||||
|
|
|
||||||
52
main.js
52
main.js
|
|
@ -24,17 +24,17 @@ const RESOLVER = app.isPackaged
|
||||||
// Built-in search engines. Users can also add their own (settings.customEngines,
|
// Built-in search engines. Users can also add their own (settings.customEngines,
|
||||||
// each { id, name, url } where the url contains "%s" for the query).
|
// each { id, name, url } where the url contains "%s" for the query).
|
||||||
const SEARCH_ENGINES = {
|
const SEARCH_ENGINES = {
|
||||||
duckduckgo: { name: "DuckDuckGo", url: (q) => "https://duckduckgo.com/?q=" + encodeURIComponent(q) },
|
duckduckgo: { name: "DuckDuckGo", sym: "🦆", url: (q) => "https://duckduckgo.com/?q=" + encodeURIComponent(q) },
|
||||||
google: { name: "Google", url: (q) => "https://www.google.com/search?q=" + encodeURIComponent(q) },
|
google: { name: "Google", sym: "🔵", url: (q) => "https://www.google.com/search?q=" + encodeURIComponent(q) },
|
||||||
brave: { name: "Brave", url: (q) => "https://search.brave.com/search?q=" + encodeURIComponent(q) },
|
brave: { name: "Brave", sym: "🦁", url: (q) => "https://search.brave.com/search?q=" + encodeURIComponent(q) },
|
||||||
bing: { name: "Bing", url: (q) => "https://www.bing.com/search?q=" + encodeURIComponent(q) },
|
bing: { name: "Bing", sym: "🔎", url: (q) => "https://www.bing.com/search?q=" + encodeURIComponent(q) },
|
||||||
startpage: { name: "Startpage", url: (q) => "https://www.startpage.com/sp/search?query=" + encodeURIComponent(q) },
|
startpage: { name: "Startpage", sym: "🛡️", url: (q) => "https://www.startpage.com/sp/search?query=" + encodeURIComponent(q) },
|
||||||
yandex: { name: "Yandex", url: (q) => "https://yandex.com/search/?text=" + encodeURIComponent(q) },
|
yandex: { name: "Yandex", sym: "🔴", url: (q) => "https://yandex.com/search/?text=" + encodeURIComponent(q) },
|
||||||
};
|
};
|
||||||
// Built-in + user-added, as flat metadata for the pickers.
|
// Built-in + user-added, as flat metadata for the pickers (sym = a display symbol).
|
||||||
function allEngines() {
|
function allEngines() {
|
||||||
const list = Object.entries(SEARCH_ENGINES).map(([id, e]) => ({ id, name: e.name, builtin: true }));
|
const list = Object.entries(SEARCH_ENGINES).map(([id, e]) => ({ id, name: e.name, sym: e.sym, builtin: true }));
|
||||||
for (const c of settings.customEngines || []) list.push({ id: c.id, name: c.name, builtin: false });
|
for (const c of settings.customEngines || []) list.push({ id: c.id, name: c.name, sym: c.sym || "🔍", builtin: false });
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
function engineUrl(id, q) {
|
function engineUrl(id, q) {
|
||||||
|
|
@ -49,11 +49,15 @@ const GATEWAY = "https://navigate.st";
|
||||||
// ---- BNS name detection (multi-TLD) --------------------------------------
|
// ---- BNS name detection (multi-TLD) --------------------------------------
|
||||||
// The engine (resolver-web.js) resolves any <label>.<tld> from the BCNR beacon.
|
// The engine (resolver-web.js) resolves any <label>.<tld> from the BCNR beacon.
|
||||||
// Two client-side sets decide how Theseus treats a name:
|
// Two client-side sets decide how Theseus treats a name:
|
||||||
// NATIVE — not in the ICANN root, so we own them outright (go straight to BCNR).
|
// NATIVE — BCNR is tried FIRST. If the TLD is also in DUAL (real ICANN),
|
||||||
// DUAL — real ICANN TLDs we ALSO offer on BCNR. These never hijack the real
|
// a BCNR NXDOMAIN or resolver failure falls through to the real
|
||||||
// web: the ICANN site loads normally, and if a BCNR name also exists
|
// web instead of erroring. If the TLD is NOT in DUAL, BCNR is
|
||||||
// we surface a passive "also on BCNR" switch (see navigateTab).
|
// authoritative and NXDOMAIN shows a clean bns:// error page.
|
||||||
const BNS_NATIVE_TLDS = new Set(["bch", "p2p", "bit", "nav", "x", "sia", "dgb", "nt"]);
|
// DUAL — Real ICANN TLDs. Used two ways:
|
||||||
|
// (a) present in NATIVE too — unlocks the ICANN fallback above;
|
||||||
|
// (b) present in DUAL only — Theseus loads the ICANN site
|
||||||
|
// normally and surfaces a passive "also on BCNR" switch.
|
||||||
|
const BNS_NATIVE_TLDS = new Set(["bch", "p2p", "bit", "nav", "x", "sia", "dgb", "nt", "de", "ru"]);
|
||||||
const BNS_DUAL_TLDS = new Set(["de", "dev", "ltd", "ru"]);
|
const BNS_DUAL_TLDS = new Set(["de", "dev", "ltd", "ru"]);
|
||||||
const REGISTRY = "BCNR"; // user-facing registry label (Bitcoin Cash Name Registry)
|
const REGISTRY = "BCNR"; // user-facing registry label (Bitcoin Cash Name Registry)
|
||||||
const tldOf = (host) => {
|
const tldOf = (host) => {
|
||||||
|
|
@ -633,11 +637,27 @@ async function navigateTab(id, input) {
|
||||||
async function loadBns(t, id, host, rest, tld) {
|
async function loadBns(t, id, host, rest, tld) {
|
||||||
const registry = registryOf(tld);
|
const registry = registryOf(tld);
|
||||||
if (id === activeId) pushNav({ host, kind: "resolving", tld, registry });
|
if (id === activeId) pushNav({ host, kind: "resolving", tld, registry });
|
||||||
|
// A NATIVE TLD that is ALSO in DUAL is a real ICANN TLD — BCNR is priority,
|
||||||
|
// but a miss (NXDOMAIN or resolver failure) falls through to the real web so
|
||||||
|
// users aren't locked out of e.g. bank.de because a name isn't on chain or
|
||||||
|
// the beacon is unreachable.
|
||||||
|
const canFallbackToWeb = BNS_DUAL_TLDS.has(tld);
|
||||||
|
const fallbackToWeb = async (reason) => {
|
||||||
|
t.url = "https://" + host + (rest === "/" ? "" : rest);
|
||||||
|
try { await t.view.webContents.loadURL(t.url); } catch {}
|
||||||
|
t.prov = { host, kind: "web", note: `fallback: ${reason}`, tld, registry };
|
||||||
|
if (id === activeId) pushNav(t.prov);
|
||||||
|
emitTabs();
|
||||||
|
};
|
||||||
let entry;
|
let entry;
|
||||||
try { entry = await resolveHost(host); }
|
try { entry = await resolveHost(host); }
|
||||||
catch (e) { setLoading(t, false); t.prov = { host, kind: "error", error: e.message, tld, registry }; if (id === activeId) pushNav(t.prov); return; }
|
catch (e) {
|
||||||
|
if (canFallbackToWeb) { setLoading(t, false); return fallbackToWeb("BCNR unreachable"); }
|
||||||
|
setLoading(t, false); t.prov = { host, kind: "error", error: e.message, tld, registry }; if (id === activeId) pushNav(t.prov); return;
|
||||||
|
}
|
||||||
t.url = host + (rest === "/" ? "" : rest);
|
t.url = host + (rest === "/" ? "" : rest);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
|
if (canFallbackToWeb) { setLoading(t, false); return fallbackToWeb("no BCNR record"); }
|
||||||
t.prov = { host, kind: "nxdomain", tld, registry };
|
t.prov = { host, kind: "nxdomain", tld, registry };
|
||||||
await t.view.webContents.loadURL(`bns://${host}/`);
|
await t.view.webContents.loadURL(`bns://${host}/`);
|
||||||
if (id === activeId) pushNav(t.prov); emitTabs(); return;
|
if (id === activeId) pushNav(t.prov); emitTabs(); return;
|
||||||
|
|
@ -678,7 +698,7 @@ ipcMain.handle("add-engine", (_e, eng) => {
|
||||||
if (eng && eng.name && eng.url && String(eng.url).includes("%s")) {
|
if (eng && eng.name && eng.url && String(eng.url).includes("%s")) {
|
||||||
const id = "custom-" + Date.now().toString(36);
|
const id = "custom-" + Date.now().toString(36);
|
||||||
settings.customEngines = [...(settings.customEngines || []),
|
settings.customEngines = [...(settings.customEngines || []),
|
||||||
{ id, name: String(eng.name).slice(0, 40), url: String(eng.url).slice(0, 400) }];
|
{ id, name: String(eng.name).slice(0, 40), sym: String(eng.sym || "🔍").slice(0, 4), url: String(eng.url).slice(0, 400) }];
|
||||||
settings.searchEngine = id; // select the one just added
|
settings.searchEngine = id; // select the one just added
|
||||||
saveSettings(); emitEngines();
|
saveSettings(); emitEngines();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
.btn{border:1px solid #d6ff3d55;background:rgba(214,255,61,.12);color:#eaffb0;border-radius:8px;padding:8px 14px;font-size:13px;cursor:pointer}
|
.btn{border:1px solid #d6ff3d55;background:rgba(214,255,61,.12);color:#eaffb0;border-radius:8px;padding:8px 14px;font-size:13px;cursor:pointer}
|
||||||
.btn:hover{background:rgba(214,255,61,.22)}
|
.btn:hover{background:rgba(214,255,61,.22)}
|
||||||
.ceng{display:flex;align-items:center;gap:8px;background:#10151f;border:1px solid var(--line);border-radius:8px;padding:6px 10px;margin-bottom:6px;font-size:13px}
|
.ceng{display:flex;align-items:center;gap:8px;background:#10151f;border:1px solid var(--line);border-radius:8px;padding:6px 10px;margin-bottom:6px;font-size:13px}
|
||||||
|
.ceng .cs{font-size:14px;flex:none}
|
||||||
.ceng .cn{font-weight:600} .ceng .cu{color:var(--dim);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
|
.ceng .cn{font-weight:600} .ceng .cu{color:var(--dim);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
|
||||||
.ceng .cx{cursor:pointer;color:#8b98a9;border:none;background:transparent;font-size:13px} .ceng .cx:hover{color:#f6768a}
|
.ceng .cx{cursor:pointer;color:#8b98a9;border:none;background:transparent;font-size:13px} .ceng .cx:hover{color:#f6768a}
|
||||||
.cempty{color:var(--dim);font-size:12.5px}
|
.cempty{color:var(--dim);font-size:12.5px}
|
||||||
|
|
@ -83,6 +84,7 @@
|
||||||
<div class="d">Name it, and give a URL with <code>%s</code> where the query goes — e.g. <code>https://searx.be/search?q=%s</code></div></div>
|
<div class="d">Name it, and give a URL with <code>%s</code> where the query goes — e.g. <code>https://searx.be/search?q=%s</code></div></div>
|
||||||
<div id="customList"></div>
|
<div id="customList"></div>
|
||||||
<div class="addeng">
|
<div class="addeng">
|
||||||
|
<input id="engSym" placeholder="🔍" style="max-width:52px;text-align:center;flex:none">
|
||||||
<input id="engName" placeholder="Name (e.g. SearXNG)">
|
<input id="engName" placeholder="Name (e.g. SearXNG)">
|
||||||
<input id="engUrl" placeholder="https://example.com/search?q=%s">
|
<input id="engUrl" placeholder="https://example.com/search?q=%s">
|
||||||
<button id="engAdd" class="btn">Add</button>
|
<button id="engAdd" class="btn">Add</button>
|
||||||
|
|
@ -176,12 +178,12 @@
|
||||||
const sel = document.getElementById("searchEngine");
|
const sel = document.getElementById("searchEngine");
|
||||||
const esc = (s) => String(s || "").replace(/</g, "<");
|
const esc = (s) => String(s || "").replace(/</g, "<");
|
||||||
function renderEngines(d) {
|
function renderEngines(d) {
|
||||||
sel.innerHTML = d.engines.map((e) => `<option value="${e.id}">${esc(e.name)}</option>`).join("");
|
sel.innerHTML = d.engines.map((e) => `<option value="${e.id}">${(e.sym ? e.sym + " " : "")}${esc(e.name)}</option>`).join("");
|
||||||
sel.value = d.current;
|
sel.value = d.current;
|
||||||
const custom = d.engines.filter((e) => !e.builtin);
|
const custom = d.engines.filter((e) => !e.builtin);
|
||||||
const list = document.getElementById("customList");
|
const list = document.getElementById("customList");
|
||||||
list.innerHTML = custom.length
|
list.innerHTML = custom.length
|
||||||
? custom.map((e) => `<div class="ceng"><span class="cn">${esc(e.name)}</span><span class="cu">${esc(e.id)}</span><button class="cx" data-id="${e.id}" title="Remove">✕</button></div>`).join("")
|
? custom.map((e) => `<div class="ceng"><span class="cs">${e.sym || "🔍"}</span><span class="cn">${esc(e.name)}</span><span class="cu">${esc(e.id)}</span><button class="cx" data-id="${e.id}" title="Remove">✕</button></div>`).join("")
|
||||||
: `<div class="cempty">No custom engines yet.</div>`;
|
: `<div class="cempty">No custom engines yet.</div>`;
|
||||||
list.querySelectorAll(".cx").forEach((b) => b.onclick = () => C.removeEngine(b.dataset.id).then(renderEngines));
|
list.querySelectorAll(".cx").forEach((b) => b.onclick = () => C.removeEngine(b.dataset.id).then(renderEngines));
|
||||||
}
|
}
|
||||||
|
|
@ -197,8 +199,12 @@
|
||||||
document.getElementById("engAdd").onclick = () => {
|
document.getElementById("engAdd").onclick = () => {
|
||||||
const name = document.getElementById("engName").value.trim();
|
const name = document.getElementById("engName").value.trim();
|
||||||
const url = document.getElementById("engUrl").value.trim();
|
const url = document.getElementById("engUrl").value.trim();
|
||||||
|
const sym = document.getElementById("engSym").value.trim();
|
||||||
if (!name || !url.includes("%s")) { alert("Enter a name and a URL containing %s (where the query goes)."); return; }
|
if (!name || !url.includes("%s")) { alert("Enter a name and a URL containing %s (where the query goes)."); return; }
|
||||||
C.addEngine({ name, url }).then((d) => { document.getElementById("engName").value = ""; document.getElementById("engUrl").value = ""; renderEngines(d); });
|
C.addEngine({ name, url, sym }).then((d) => {
|
||||||
|
document.getElementById("engName").value = ""; document.getElementById("engUrl").value = ""; document.getElementById("engSym").value = "";
|
||||||
|
renderEngines(d);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
C.engines().then(renderEngines);
|
C.engines().then(renderEngines);
|
||||||
// anti-fingerprinting mode selectors, with value field(s) shown on "manual"
|
// anti-fingerprinting mode selectors, with value field(s) shown on "manual"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue