fix(theseus): the default-engine control in Settings shows the engine icons
It was a native <select>, which can only show text, so each option carried an emoji in front of the name; after the engine icons moved into the build that was the one place still showing emojis. The control is now drawn by Settings with the same icons as the rows below, grouped like the toolbar picker, with arrow-key and Escape handling. Choosing a default there also repaints the toolbar at once: the generic setting write never told it.
This commit is contained in:
parent
d016453f73
commit
668914354f
2 changed files with 67 additions and 10 deletions
1
main.js
1
main.js
|
|
@ -5957,6 +5957,7 @@ ipcMain.handle("settings-set", (_e, key, val) => {
|
||||||
if (key === "webrtcMode") applyWebRTCPolicy();
|
if (key === "webrtcMode") applyWebRTCPolicy();
|
||||||
if (key === "dohMode" || key === "dohProvider" || key === "dohCustom") applyDoh();
|
if (key === "dohMode" || key === "dohProvider" || key === "dohCustom") applyDoh();
|
||||||
if (key === "gpc") applyFingerprintAll();
|
if (key === "gpc") applyFingerprintAll();
|
||||||
|
if (key === "searchEngine") emitEngines(); // the toolbar button and the picker show the default
|
||||||
if (key === "theme") applyTheme();
|
if (key === "theme") applyTheme();
|
||||||
if (key === "backgroundThrottle") applyThrottle();
|
if (key === "backgroundThrottle") applyThrottle();
|
||||||
if (["timezoneMode", "timezoneValue", "languageMode", "languageSpoof", "languageValue",
|
if (["timezoneMode", "timezoneValue", "languageMode", "languageSpoof", "languageValue",
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,19 @@
|
||||||
.eng.off .enm{color:var(--mut)}
|
.eng.off .enm{color:var(--mut)}
|
||||||
.ehdr{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);margin:14px 0 6px;padding-top:2px}
|
.ehdr{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);margin:14px 0 6px;padding-top:2px}
|
||||||
.ehdr:first-child{margin-top:0}
|
.ehdr:first-child{margin-top:0}
|
||||||
|
/* default-engine dropdown: drawn by us so it can show the engine icons (a native <select> is text-only) */
|
||||||
|
.esel{display:flex;align-items:center;gap:8px;background:#1b2330;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:6px 10px;font-size:13px;min-width:180px;cursor:pointer;user-select:none;outline:none}
|
||||||
|
.esel:focus-visible,.esel.open{border-color:#4b7bec}
|
||||||
|
.esel .eic,.eselmenu .eic{display:inline-flex;width:16px;height:16px;align-items:center;justify-content:center;flex:none}
|
||||||
|
.esel .eic .ei,.eselmenu .eic .ei{width:16px;height:16px;border-radius:3px} .esel .eic .es,.eselmenu .eic .es{font-size:14px;line-height:1}
|
||||||
|
.esel .enm{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||||
|
.esel .caret{color:var(--mut);font-size:13px;line-height:1;flex:none}
|
||||||
|
.ctxmenu.eselmenu{min-width:200px;max-height:60vh;overflow-y:auto;padding:4px}
|
||||||
|
.ctxmenu.eselmenu .ehdr{margin:6px 8px 2px;padding:0} .ctxmenu.eselmenu .ehdr:first-child{margin-top:2px}
|
||||||
|
.ctxmenu.eselmenu .mi{display:flex;align-items:center;gap:8px;padding:6px 10px}
|
||||||
|
.ctxmenu.eselmenu .mi.cur{color:var(--acid-text)} .ctxmenu.eselmenu .mi.hl{background:#ffffff10}
|
||||||
|
.ctxmenu.eselmenu .mi .chk{margin-left:auto;font-size:12px}
|
||||||
|
@media (prefers-color-scheme: light){ .esel{background:#f1f5f8;color:#253A49} .ctxmenu.eselmenu .mi.hl{background:rgba(0,0,0,.05)} }
|
||||||
/* right-click context menu for an engine row */
|
/* right-click context menu for an engine row */
|
||||||
.ctxmenu{position:fixed;z-index:9999;background:#1c222c;border:1px solid var(--line);border-radius:8px;
|
.ctxmenu{position:fixed;z-index:9999;background:#1c222c;border:1px solid var(--line);border-radius:8px;
|
||||||
box-shadow:0 12px 34px #000c;padding:4px;min-width:180px;font-size:13px;color:var(--ink)}
|
box-shadow:0 12px 34px #000c;padding:4px;min-width:180px;font-size:13px;color:var(--ink)}
|
||||||
|
|
@ -341,7 +354,7 @@
|
||||||
<p class="lede">Pick what your address bar and the toolbar dropdown search with.</p>
|
<p class="lede">Pick what your address bar and the toolbar dropdown search with.</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="txt"><div class="t">Default search engine</div><div class="d">Used when you type into the address bar.</div></div>
|
<div class="txt"><div class="t">Default search engine</div><div class="d">Used when you type into the address bar.</div></div>
|
||||||
<div class="ctl"><select id="searchEngine"></select></div>
|
<div class="ctl"><div class="esel" id="searchEngine" tabindex="0" role="combobox" aria-haspopup="listbox" aria-expanded="false" title="Default search engine"><span class="eic"></span><span class="enm"></span><span class="caret">▾</span></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" style="flex-direction:column;align-items:stretch;gap:10px">
|
<div class="row" style="flex-direction:column;align-items:stretch;gap:10px">
|
||||||
<div class="txt" style="display:flex;align-items:center;justify-content:space-between;gap:12px">
|
<div class="txt" style="display:flex;align-items:center;justify-content:space-between;gap:12px">
|
||||||
|
|
@ -892,16 +905,18 @@
|
||||||
{ key: "search", label: "Search engines" },
|
{ key: "search", label: "Search engines" },
|
||||||
{ key: "llm", label: "AI answer engines" },
|
{ key: "llm", label: "AI answer engines" },
|
||||||
];
|
];
|
||||||
|
// Default engine: our own dropdown over the ENABLED engines (the ones in the
|
||||||
|
// toolbar picker), grouped like it, each row with the engine's icon.
|
||||||
|
let selEngines = [], selCurrent = "";
|
||||||
|
function paintDefaultEngine() {
|
||||||
|
const cur = selEngines.find((e) => e.id === selCurrent) || selEngines[0];
|
||||||
|
sel.querySelector(".eic").innerHTML = cur ? engIcon(cur) : "";
|
||||||
|
sel.querySelector(".enm").textContent = cur ? cur.name : "";
|
||||||
|
}
|
||||||
function renderEngines(d) {
|
function renderEngines(d) {
|
||||||
// default engine: pick from the ENABLED engines (those shown in the dropdown)
|
|
||||||
const enabled = d.engines.filter((e) => e.enabled);
|
const enabled = d.engines.filter((e) => e.enabled);
|
||||||
// group the dropdown too, so search and LLM don't intermix
|
selEngines = enabled; selCurrent = d.current;
|
||||||
sel.innerHTML = ENGINE_KINDS.map(({ key, label }) => {
|
paintDefaultEngine();
|
||||||
const opts = enabled.filter((e) => (e.kind || "search") === key)
|
|
||||||
.map((e) => `<option value="${e.id}">${(e.sym ? e.sym + " " : "")}${esc(e.name)}</option>`).join("");
|
|
||||||
return opts ? `<optgroup label="${label}">${opts}</optgroup>` : "";
|
|
||||||
}).join("");
|
|
||||||
sel.value = d.current;
|
|
||||||
// Main list = engines the user has INSTALLED. The toggle only flips
|
// Main list = engines the user has INSTALLED. The toggle only flips
|
||||||
// enabled/disabled — the row STAYS. Right-click a row → "Remove from
|
// enabled/disabled — the row STAYS. Right-click a row → "Remove from
|
||||||
// list" is what actually removes an engine (back to the catalog for
|
// list" is what actually removes an engine (back to the catalog for
|
||||||
|
|
@ -1018,7 +1033,48 @@
|
||||||
document.addEventListener("keydown", esc);
|
document.addEventListener("keydown", esc);
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
sel.onchange = () => C.set("searchEngine", sel.value);
|
let selMenu = null;
|
||||||
|
function closeDefaultEngineMenu() { if (selMenu) { selMenu.remove(); selMenu = null; sel.classList.remove("open"); sel.setAttribute("aria-expanded", "false"); } }
|
||||||
|
function openDefaultEngineMenu() {
|
||||||
|
closeEngineMenu(); closeDefaultEngineMenu();
|
||||||
|
const m = document.createElement("div");
|
||||||
|
m.className = "ctxmenu eselmenu"; m.setAttribute("role", "listbox");
|
||||||
|
m.innerHTML = ENGINE_KINDS.map(({ key, label }) => {
|
||||||
|
const rows = selEngines.filter((e) => (e.kind || "search") === key)
|
||||||
|
.map((e) => `<div class="mi${e.id === selCurrent ? " cur" : ""}" role="option" data-id="${esc(e.id)}"><span class="eic">${engIcon(e)}</span><span class="enm">${esc(e.name)}</span>${e.id === selCurrent ? '<span class="chk">✓</span>' : ""}</div>`).join("");
|
||||||
|
return rows ? `<div class="ehdr">${label}</div>${rows}` : "";
|
||||||
|
}).join("");
|
||||||
|
document.body.appendChild(m);
|
||||||
|
const r = sel.getBoundingClientRect(), mr = m.getBoundingClientRect();
|
||||||
|
const vw = document.documentElement.clientWidth, vh = document.documentElement.clientHeight;
|
||||||
|
m.style.minWidth = r.width + "px";
|
||||||
|
m.style.left = Math.max(6, Math.min(r.left, vw - mr.width - 6)) + "px";
|
||||||
|
m.style.top = (r.bottom + 4 + mr.height > vh - 6 ? Math.max(6, r.top - 4 - mr.height) : r.bottom + 4) + "px";
|
||||||
|
const items = [...m.querySelectorAll(".mi")];
|
||||||
|
let hl = Math.max(0, items.findIndex((i) => i.dataset.id === selCurrent));
|
||||||
|
const paintHl = () => { items.forEach((i, k) => i.classList.toggle("hl", k === hl)); items[hl]?.scrollIntoView({ block: "nearest" }); };
|
||||||
|
const choose = (id) => { closeDefaultEngineMenu(); if (id && id !== selCurrent) { selCurrent = id; paintDefaultEngine(); C.set("searchEngine", id); } sel.focus(); };
|
||||||
|
items.forEach((i, k) => { i.onmouseenter = () => { hl = k; paintHl(); }; i.onclick = () => choose(i.dataset.id); });
|
||||||
|
paintHl();
|
||||||
|
const key = (ev) => {
|
||||||
|
if (ev.key === "Escape") { ev.preventDefault(); closeDefaultEngineMenu(); sel.focus(); }
|
||||||
|
else if (ev.key === "ArrowDown") { ev.preventDefault(); hl = Math.min(items.length - 1, hl + 1); paintHl(); }
|
||||||
|
else if (ev.key === "ArrowUp") { ev.preventDefault(); hl = Math.max(0, hl - 1); paintHl(); }
|
||||||
|
else if (ev.key === "Enter" || ev.key === " ") { ev.preventDefault(); choose(items[hl]?.dataset.id); }
|
||||||
|
else if (ev.key === "Tab") closeDefaultEngineMenu();
|
||||||
|
};
|
||||||
|
const off = (ev) => { if (!m.contains(ev.target) && ev.target !== sel && !sel.contains(ev.target)) closeDefaultEngineMenu(); };
|
||||||
|
const cleanup = () => { document.removeEventListener("mousedown", off); document.removeEventListener("keydown", key, true); window.removeEventListener("blur", cleanup); };
|
||||||
|
document.addEventListener("mousedown", off);
|
||||||
|
document.addEventListener("keydown", key, true);
|
||||||
|
window.addEventListener("blur", closeDefaultEngineMenu, { once: true });
|
||||||
|
const origRemove = m.remove.bind(m); m.remove = () => { cleanup(); origRemove(); };
|
||||||
|
selMenu = m; sel.classList.add("open"); sel.setAttribute("aria-expanded", "true");
|
||||||
|
}
|
||||||
|
sel.onclick = () => (selMenu ? closeDefaultEngineMenu() : openDefaultEngineMenu());
|
||||||
|
sel.onkeydown = (ev) => { if (selMenu) return; if (ev.key === "Enter" || ev.key === " " || ev.key === "ArrowDown" || ev.key === "ArrowUp") { ev.preventDefault(); openDefaultEngineMenu(); } };
|
||||||
|
// The toolbar picker changes the same setting; keep the control in step.
|
||||||
|
if (C.onSettingsUpdate) C.onSettingsUpdate((next) => { if (next && typeof next.searchEngine === "string" && next.searchEngine !== selCurrent) { selCurrent = next.searchEngine; paintDefaultEngine(); } });
|
||||||
// appearance (theme) — three visual cards: system | light | dark. Any
|
// appearance (theme) — three visual cards: system | light | dark. Any
|
||||||
// unrecognised saved value falls back to "system" (follow the OS).
|
// unrecognised saved value falls back to "system" (follow the OS).
|
||||||
const th = document.getElementById("theme");
|
const th = document.getElementById("theme");
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue