feat(theseus/find): Ctrl+F opens an in-page find bar
Standard browser Ctrl+F support:
- main.js: Ctrl+F caught in the shared before-input-event handler (same
place as F12 / reload shortcuts) fires 'find-open' to chrome.
Two new IPC handlers proxy to activeTab().view.webContents:
find-in-page(query, {forward, findNext, matchCase})
find-stop
Each tab's webContents listens for 'found-in-page' and forwards
{activeMatchOrdinal, matches, finalUpdate} back to chrome via
'find-result' — only when it's the active tab so the bar doesn't
update from a background tab's stale match count.
- preload.js: exposes onFindOpen / findInPage / findStop / onFindResult.
- chrome.html: adds a .findbar strip below the bookmarks bar (part of
chrome-view height like .tordisc / .bcnrbar, so syncHeight picks it
up). Input + "N of M" counter + prev/next/close buttons. Enter jumps
next, Shift+Enter previous, Esc closes. Typing runs findNext:false
(fresh search); pressing Enter runs findNext:true (walk matches).
No-match state paints the input border red.
This commit is contained in:
parent
b5fc42e4d4
commit
cf0951b754
3 changed files with 115 additions and 0 deletions
73
chrome.html
73
chrome.html
|
|
@ -295,6 +295,18 @@
|
|||
.logo .gear { font-size: 12px; opacity: .8; }
|
||||
/* favorites bar — new-tab page only, single compact row */
|
||||
.bookmarks { display: flex; align-items: center; gap: 4px; padding: 3px 10px 5px; height: 26px; overflow: hidden; }
|
||||
/* Ctrl+F find-in-page bar. Sits below the address bar (above tabs body),
|
||||
~32px tall so it's part of the chrome view height like .tordisc / .bcnrbar.
|
||||
syncHeight in the tabs handler picks it up automatically. */
|
||||
.findbar { display: flex; align-items: center; gap: 6px; padding: 5px 10px; border-top: 1px solid var(--line); background: var(--surface); }
|
||||
.findbar input { flex: 1; min-width: 120px; padding: 4px 8px; border-radius: 6px; background: var(--surface2); color: var(--ink);
|
||||
border: 1px solid var(--line); font: inherit; font-size: 12.5px; outline: none; }
|
||||
.findbar input:focus { border-color: color-mix(in srgb, var(--acid) 55%, transparent); }
|
||||
.findbar input.nomatch { border-color: rgba(246,118,138,.55); background: rgba(246,118,138,.10); }
|
||||
.findbar .findcount { font-size: 12px; color: var(--dim); font-variant-numeric: tabular-nums; min-width: 46px; text-align: right; }
|
||||
.findbar .findbtn { border: 1px solid var(--line); background: var(--surface2); color: var(--ink); border-radius: 6px;
|
||||
padding: 3px 8px; font-size: 13px; cursor: pointer; }
|
||||
.findbar .findbtn:hover { background: var(--surface); border-color: color-mix(in srgb, var(--acid) 35%, transparent); }
|
||||
.bm { display: flex; align-items: center; gap: 6px; max-width: 180px; padding: 3px 9px; border-radius: 6px;
|
||||
background: var(--surface2); border: 1px solid var(--hover); color: var(--mut); font-size: 12px; cursor: pointer; white-space: nowrap; }
|
||||
.bm:hover { background: var(--active); color: var(--ink); }
|
||||
|
|
@ -397,6 +409,13 @@
|
|||
</div>
|
||||
<div class="loadbar" id="loadbar"></div>
|
||||
<div class="bookmarks" id="bookmarks" hidden></div>
|
||||
<div class="findbar" id="findbar" hidden>
|
||||
<input id="findInput" type="text" placeholder="Find in page" spellcheck="false" autocomplete="off">
|
||||
<span class="findcount" id="findCount">–</span>
|
||||
<button class="findbtn" id="findPrev" title="Previous (Shift+Enter)">↑</button>
|
||||
<button class="findbtn" id="findNext" title="Next (Enter)">↓</button>
|
||||
<button class="findbtn" id="findClose" title="Close (Esc)">✕</button>
|
||||
</div>
|
||||
<div id="tordisc" class="tordisc" hidden>
|
||||
Onion mode hides your IP from sites and your ISP (including .bch lookups).
|
||||
It is <b>not full anonymity</b> — this browser can still be fingerprinted; for that use the Tor Browser.
|
||||
|
|
@ -1303,6 +1322,60 @@
|
|||
T.getSettings && T.getSettings().then(applyBarSizes);
|
||||
T.onSettingsUpdate && T.onSettingsUpdate(applyBarSizes);
|
||||
|
||||
// ---- Ctrl+F find-in-page bar ----
|
||||
// Chrome renderer owns the UI; main proxies findInPage / stopFindInPage
|
||||
// and forwards Chromium's found-in-page events back so we can render
|
||||
// "N of M". Enter jumps to next, Shift+Enter to previous, Esc closes.
|
||||
const findbar = $("findbar");
|
||||
const findInput = $("findInput");
|
||||
const findCount = $("findCount");
|
||||
let findQuery = "";
|
||||
function openFind() {
|
||||
findbar.hidden = false;
|
||||
syncHeight();
|
||||
setTimeout(() => { findInput.focus(); findInput.select(); }, 0);
|
||||
// Re-highlight the current query if there was one.
|
||||
if (findInput.value.trim()) T.findInPage && T.findInPage(findInput.value.trim(), { findNext: true });
|
||||
}
|
||||
function closeFind() {
|
||||
findbar.hidden = true;
|
||||
findQuery = "";
|
||||
findCount.textContent = "–";
|
||||
findInput.classList.remove("nomatch");
|
||||
T.findStop && T.findStop();
|
||||
syncHeight();
|
||||
}
|
||||
function stepFind(forward) {
|
||||
const q = findInput.value.trim();
|
||||
if (!q) return;
|
||||
findQuery = q;
|
||||
T.findInPage && T.findInPage(q, { forward, findNext: true });
|
||||
}
|
||||
T.onFindOpen && T.onFindOpen(openFind);
|
||||
T.onFindResult && T.onFindResult((r) => {
|
||||
if (r.matches === 0) {
|
||||
findCount.textContent = "0";
|
||||
findInput.classList.add("nomatch");
|
||||
} else {
|
||||
findCount.textContent = r.activeMatchOrdinal + " / " + r.matches;
|
||||
findInput.classList.remove("nomatch");
|
||||
}
|
||||
});
|
||||
findInput.addEventListener("input", () => {
|
||||
const q = findInput.value.trim();
|
||||
if (!q) { T.findStop && T.findStop(); findCount.textContent = "–"; findInput.classList.remove("nomatch"); return; }
|
||||
// findNext: false → new query starts a fresh search from the top.
|
||||
T.findInPage && T.findInPage(q, { findNext: q === findQuery, forward: true });
|
||||
findQuery = q;
|
||||
});
|
||||
findInput.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") { e.preventDefault(); stepFind(!e.shiftKey); }
|
||||
else if (e.key === "Escape") { e.preventDefault(); closeFind(); }
|
||||
});
|
||||
$("findPrev").onclick = () => stepFind(false);
|
||||
$("findNext").onclick = () => stepFind(true);
|
||||
$("findClose").onclick = closeFind;
|
||||
|
||||
// Drag handles: right edge of .urlwrap resizes the URL bar; left edge of
|
||||
// .searchbox resizes the search box. Persists to settings.urlBarWidthPx /
|
||||
// searchBoxWidthPx on release, which then wins over the size preset.
|
||||
|
|
|
|||
35
main.js
35
main.js
|
|
@ -2085,6 +2085,12 @@ function createTab(initial, opts = {}) {
|
|||
const next = (urls && urls[0]) || null;
|
||||
if (tab.favicon !== next) { tab.favicon = next; emitTabs(); }
|
||||
});
|
||||
// Chromium fires found-in-page on every findInPage call + on subsequent
|
||||
// match walks. Forward to chrome so the find bar shows "N of M".
|
||||
wc.on("found-in-page", (_e, r) => {
|
||||
if (tab.id !== activeId) return;
|
||||
try { chrome?.webContents.send("find-result", { activeMatchOrdinal: r.activeMatchOrdinal, matches: r.matches, finalUpdate: r.finalUpdate }); } catch {}
|
||||
});
|
||||
wc.on("did-navigate", () => { refreshTabUrl(tab); emitTabs(); historyAdd(tab.url, tab.title); });
|
||||
wc.on("did-navigate-in-page", () => { refreshTabUrl(tab); emitTabs(); historyAdd(tab.url, tab.title); });
|
||||
wc.on("did-start-loading", () => setLoading(tab, true));
|
||||
|
|
@ -2923,6 +2929,27 @@ ipcMain.handle("open-update-download", (_e, url) => {
|
|||
} catch (e) { console.warn("update download failed:", e?.message); return false; }
|
||||
});
|
||||
ipcMain.handle("dismiss-update", () => { updateDismissedThisSession = true; emitUpdateAvailable(); return true; });
|
||||
// Find-in-page. Chrome renderer's find bar drives this: the bar sends
|
||||
// `find-in-page` with the query + direction; main proxies to the active
|
||||
// tab's webContents.findInPage. Chromium raises `found-in-page` on each
|
||||
// keystroke with the running match count / active match ordinal; we
|
||||
// forward that back to chrome so the bar can render "3 of 12".
|
||||
ipcMain.handle("find-in-page", (_e, query, opts = {}) => {
|
||||
const t = activeTab(); if (!t) return false;
|
||||
if (typeof query !== "string" || !query) { try { t.view.webContents.stopFindInPage("clearSelection"); } catch {} return false; }
|
||||
try {
|
||||
t.view.webContents.findInPage(query, {
|
||||
forward: opts.forward !== false,
|
||||
findNext: !!opts.findNext, // false = new query; true = jump next/prev
|
||||
matchCase: !!opts.matchCase,
|
||||
});
|
||||
return true;
|
||||
} catch { return false; }
|
||||
});
|
||||
ipcMain.handle("find-stop", () => {
|
||||
const t = activeTab(); if (!t) return false;
|
||||
try { t.view.webContents.stopFindInPage("clearSelection"); return true; } catch { return false; }
|
||||
});
|
||||
ipcMain.handle("recheck-update", async () => {
|
||||
// Manual "Check for updates" also un-dismisses any chip the user closed
|
||||
// in this session — they're actively asking to see the status, so honour
|
||||
|
|
@ -3992,6 +4019,14 @@ app.on("web-contents-created", (_event, wc) => {
|
|||
toggleSidebar();
|
||||
return e.preventDefault();
|
||||
}
|
||||
// Find-in-page: Ctrl+F opens the find bar in chrome. Chrome renderer
|
||||
// owns the UI (input, next/prev, count, close); it drives the active
|
||||
// tab's webContents.findInPage via IPC (find-in-page / find-stop).
|
||||
const isF = input.key === "F" || input.key === "f";
|
||||
if (input.control && !input.shift && !input.alt && isF) {
|
||||
try { chrome?.webContents.send("find-open"); } catch {}
|
||||
return e.preventDefault();
|
||||
}
|
||||
// DevTools: F12 or Ctrl+Shift+I toggles Chromium DevTools on the active
|
||||
// TAB (not the chrome/overlay that received the keystroke — a user
|
||||
// debugging a page wants the page's inspector, always). Detach into
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ contextBridge.exposeInMainWorld("theseus", {
|
|||
getSettings: () => ipcRenderer.invoke("settings-get"),
|
||||
setSetting: (key, val) => ipcRenderer.invoke("settings-set", key, val),
|
||||
onSettingsUpdate: (cb) => ipcRenderer.on("settings-update", (_e, d) => cb(d)),
|
||||
// Find-in-page. main.js fires 'find-open' on Ctrl+F; chrome renderer
|
||||
// owns the bar UI and drives findInPage / stopFindInPage via these
|
||||
// wrappers. Match count / active ordinal comes back through onFindResult.
|
||||
onFindOpen: (cb) => ipcRenderer.on("find-open", () => cb()),
|
||||
findInPage: (query, opts) => ipcRenderer.invoke("find-in-page", query, opts || {}),
|
||||
findStop: () => ipcRenderer.invoke("find-stop"),
|
||||
onFindResult: (cb) => ipcRenderer.on("find-result", (_e, d) => cb(d)),
|
||||
toggleSiteInfo: (rect) => ipcRenderer.invoke("toggle-site-info", rect),
|
||||
switchToBcnr: () => ipcRenderer.invoke("switch-to-bcnr"),
|
||||
setChromeHeight: (h) => ipcRenderer.invoke("set-chrome-height", h),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue