Theseus navigator UI overhaul, BNS-only indexer, site restructure
Theseus Navigator: - Firefox-style toolbar: SVG back/fwd/reload/home buttons - security padlock badge in the address bar opens a floating site-info overlay VIEW (on top of the page, never pushes content down) - Edge-style favorites bar (shown only on the new-tab/home page) - search-engine picker (DuckDuckGo/Google/Bing/Brave/Startpage) - Theseus logo moved right, opens Settings; native menu removed - address bar doubles as search; bottom resolver status line Argus: - BNS-only indexer: an electrum endpoint over a bare BCHN node (watches just the beacon, caches txs so no txindex needed) + test - registry lifecycle / portfolio / name-index modules Site restructure (nav/apps/store/hermes/choose) + runbook notes.
This commit is contained in:
parent
7898e78ff4
commit
89c80a0103
5 changed files with 264 additions and 100 deletions
227
chrome.html
227
chrome.html
|
|
@ -3,6 +3,8 @@
|
||||||
<head><meta charset="utf-8">
|
<head><meta charset="utf-8">
|
||||||
<style>
|
<style>
|
||||||
:root { color-scheme: dark; font-family: system-ui, sans-serif; }
|
:root { color-scheme: dark; font-family: system-ui, sans-serif; }
|
||||||
|
/* `display:flex/grid` otherwise overrides the HTML hidden attribute — force it. */
|
||||||
|
[hidden] { display: none !important; }
|
||||||
body { margin: 0; height: 100vh; background: #0f1420; color: #e7eaf1; user-select: none; overflow: hidden; }
|
body { margin: 0; height: 100vh; background: #0f1420; color: #e7eaf1; user-select: none; overflow: hidden; }
|
||||||
/* tab strip */
|
/* tab strip */
|
||||||
.tabs { display: flex; align-items: flex-end; gap: 4px; padding: 6px 8px 0; height: 34px; }
|
.tabs { display: flex; align-items: flex-end; gap: 4px; padding: 6px 8px 0; height: 34px; }
|
||||||
|
|
@ -18,7 +20,6 @@
|
||||||
/* toolbar */
|
/* toolbar */
|
||||||
.bar { display: flex; gap: 6px; align-items: center; padding: 6px 10px; }
|
.bar { display: flex; gap: 6px; align-items: center; padding: 6px 10px; }
|
||||||
.nav { display: flex; gap: 1px; }
|
.nav { display: flex; gap: 1px; }
|
||||||
/* Firefox-style round-rect icon buttons: 16px stroke glyph in a 34x32 hit target */
|
|
||||||
.ic { width: 34px; height: 32px; display: grid; place-items: center; border-radius: 8px; cursor: pointer;
|
.ic { width: 34px; height: 32px; display: grid; place-items: center; border-radius: 8px; cursor: pointer;
|
||||||
color: #c5cdda; background: transparent; border: none; padding: 0; }
|
color: #c5cdda; background: transparent; border: none; padding: 0; }
|
||||||
.ic svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7;
|
.ic svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7;
|
||||||
|
|
@ -26,41 +27,51 @@
|
||||||
.ic:hover { background: #ffffff14; color: #fff; }
|
.ic:hover { background: #ffffff14; color: #fff; }
|
||||||
.ic:active { background: #ffffff22; }
|
.ic:active { background: #ffffff22; }
|
||||||
.ic:disabled { opacity: .28; cursor: default; background: transparent; }
|
.ic:disabled { opacity: .28; cursor: default; background: transparent; }
|
||||||
/* clickable Theseus logo = site-info button */
|
/* address bar with a security badge on the left + bookmark star on the right */
|
||||||
.logo { display: flex; align-items: center; gap: 6px; font-weight: 700; color: #d6ff3d; white-space: nowrap;
|
.urlwrap { flex: 1; display: flex; align-items: center; gap: 4px; background: #1b2330; border: 1px solid #ffffff22;
|
||||||
padding: 5px 10px; margin: 0 2px; border: 1px solid transparent; border-radius: 8px; cursor: pointer;
|
border-radius: 999px; padding: 0 6px 0 4px; }
|
||||||
background: transparent; font-size: 13px; }
|
|
||||||
.logo:hover { background: rgba(214,255,61,.10); border-color: #d6ff3d33; }
|
|
||||||
.logo.open { background: rgba(214,255,61,.14); border-color: #d6ff3d55; }
|
|
||||||
.logo .chev { color: #9fb04e; font-size: 9px; }
|
|
||||||
input { padding: 8px 13px; border-radius: 999px; border: 1px solid #ffffff22; background: #1b2330; color: inherit; font-size: 13.5px; outline: none; }
|
|
||||||
input:focus { border-color: #4b7bec; }
|
|
||||||
.urlwrap { flex: 1; display: flex; align-items: center; gap: 6px; background: #1b2330; border: 1px solid #ffffff22;
|
|
||||||
border-radius: 999px; padding-right: 8px; }
|
|
||||||
.urlwrap:focus-within { border-color: #4b7bec; }
|
.urlwrap:focus-within { border-color: #4b7bec; }
|
||||||
#url { flex: 1; border: none; background: transparent; border-radius: 999px; }
|
/* Firefox-style padlock badge */
|
||||||
#url:focus { border: none; }
|
.secbadge { display: grid; place-items: center; width: 28px; height: 26px; border-radius: 6px; border: none;
|
||||||
#search { width: 180px; }
|
background: transparent; cursor: pointer; padding: 0; }
|
||||||
|
.secbadge:hover { background: #ffffff14; }
|
||||||
|
.secbadge svg { width: 14px; height: 14px; }
|
||||||
|
.secbadge svg .shk { fill: none; stroke: #a9b3c2; stroke-width: 1.3; }
|
||||||
|
.secbadge svg .bdy { fill: #a9b3c2; }
|
||||||
|
.secbadge.warn svg .shk { stroke: #f6ad55; } .secbadge.warn svg .bdy { fill: #f6ad55; }
|
||||||
|
.star { border: none; background: transparent; cursor: pointer; color: #8b98a9; font-size: 15px; padding: 2px 4px; border-radius: 6px; }
|
||||||
|
.star:hover { background: #ffffff14; color: #fff; } .star.on { color: #ffd23d; }
|
||||||
|
input { padding: 7px 6px; border-radius: 999px; border: none; background: transparent; color: inherit; font-size: 13.5px; outline: none; }
|
||||||
|
#url { flex: 1; }
|
||||||
|
/* search box + engine picker */
|
||||||
|
.searchbox { display: flex; align-items: center; gap: 2px; background: #1b2330; border: 1px solid #ffffff22;
|
||||||
|
border-radius: 999px; padding-left: 8px; }
|
||||||
|
.searchbox:focus-within { border-color: #4b7bec; }
|
||||||
|
.searchbox .mag { color: #8b98a9; font-size: 12px; }
|
||||||
|
#search { width: 150px; }
|
||||||
|
.engine { background: #10151f; color: #b9c2d0; border: none; border-left: 1px solid #ffffff1a; border-radius: 0 999px 999px 0;
|
||||||
|
font-size: 11.5px; padding: 6px 6px; outline: none; cursor: pointer; }
|
||||||
|
.reg { font-size: 10.5px; letter-spacing: .03em; padding: 2px 8px; border-radius: 999px; white-space: nowrap;
|
||||||
|
background: rgba(214,255,61,.13); color: #d6ff3d; border: 1px solid #d6ff3d33; }
|
||||||
.tor { padding: 7px 10px; border-radius: 8px; border: 1px solid #ffffff22; background: #2b2f3a; color: #fff; cursor: pointer; white-space: nowrap; font-size: 12.5px; }
|
.tor { padding: 7px 10px; border-radius: 8px; border: 1px solid #ffffff22; background: #2b2f3a; color: #fff; cursor: pointer; white-space: nowrap; font-size: 12.5px; }
|
||||||
.tor.connecting { background: #7a5c14; } .tor.on { background: #6b3fa0; }
|
.tor.connecting { background: #7a5c14; } .tor.on { background: #6b3fa0; }
|
||||||
.sep { width: 1px; height: 22px; background: #ffffff1a; margin: 0 4px; }
|
/* Theseus logo — moved to the right, opens Settings */
|
||||||
.searchbox { display: flex; align-items: center; gap: 4px; }
|
.logo { display: flex; align-items: center; gap: 6px; font-weight: 700; color: #d6ff3d; white-space: nowrap;
|
||||||
.searchbox .mag { color: #8b98a9; font-size: 13px; }
|
padding: 6px 12px; border: 1px solid #d6ff3d33; border-radius: 8px; cursor: pointer;
|
||||||
/* registry/TLD badge — trusted chrome, painted by the browser not the page */
|
background: rgba(214,255,61,.08); font-size: 13px; }
|
||||||
.reg { font-size: 10.5px; letter-spacing: .03em; padding: 2px 9px; border-radius: 999px;
|
.logo:hover { background: rgba(214,255,61,.16); }
|
||||||
background: rgba(214,255,61,.13); color: #d6ff3d; border: 1px solid #d6ff3d33; white-space: nowrap; }
|
.logo .gear { font-size: 12px; opacity: .8; }
|
||||||
/* site-info dropdown panel (opened by the logo) */
|
/* bookmarks / saved-pages bar (Edge-style) */
|
||||||
.siteinfo { font-size: 12px; color: #dbe3ef; background: #141b28; border-top: 1px solid #ffffff12;
|
.bookmarks { display: flex; align-items: center; gap: 4px; padding: 3px 10px 5px; overflow: hidden; }
|
||||||
padding: 10px 16px; display: grid; grid-template-columns: auto 1fr; gap: 5px 14px; }
|
.bm { display: flex; align-items: center; gap: 6px; max-width: 180px; padding: 3px 9px; border-radius: 6px;
|
||||||
.siteinfo .k { color: #7f8aa0; }
|
background: #171e2a; border: 1px solid #ffffff10; color: #c5cdda; font-size: 12px; cursor: pointer; }
|
||||||
.siteinfo .v { color: #e7eaf1; }
|
.bm:hover { background: #202a3a; color: #fff; }
|
||||||
.siteinfo .v code { font-family: ui-monospace, monospace; color: #bfeae4; }
|
.bm .bt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.siteinfo .dotv { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #5e6678; margin-right: 6px; vertical-align: middle; }
|
.bm .bx { opacity: 0; cursor: pointer; font-size: 11px; } .bm:hover .bx { opacity: .55; }
|
||||||
.siteinfo .dotv.chain { background: #d6ff3d; } .siteinfo .dotv.sia { background: #b39ddb; }
|
.bm .bx:hover { opacity: 1; color: #f6768a; }
|
||||||
.siteinfo .dotv.server { background: #4fd1a5; } .siteinfo .dotv.web { background: #8b93a7; } .siteinfo .dotv.err { background: #f6768a; }
|
.bm-empty { color: #5e6678; font-size: 11.5px; padding: 3px 4px; }
|
||||||
.tordisc { font-size: 11.5px; color: #d9c7f2; background: #2a1c40; border-top: 1px solid #6b3fa055; padding: 5px 14px; }
|
.tordisc { font-size: 11.5px; color: #d9c7f2; background: #2a1c40; border-top: 1px solid #6b3fa055; padding: 5px 14px; }
|
||||||
.tordisc a { color: #d6ff3d; }
|
.tordisc a { color: #d6ff3d; }
|
||||||
/* passive "also on BCNR" bar — dual-use TLD switch (never hijacks the web) */
|
|
||||||
.bcnrbar { display: flex; align-items: center; gap: 10px; font-size: 11.5px; color: #dbead0;
|
.bcnrbar { display: flex; align-items: center; gap: 10px; font-size: 11.5px; color: #dbead0;
|
||||||
background: #1a2417; border-top: 1px solid #d6ff3d33; padding: 6px 14px; }
|
background: #1a2417; border-top: 1px solid #d6ff3d33; padding: 6px 14px; }
|
||||||
.bcnrbar .breg { font-size: 10px; letter-spacing: .03em; padding: 1px 7px; border-radius: 999px;
|
.bcnrbar .breg { font-size: 10px; letter-spacing: .03em; padding: 1px 7px; border-radius: 999px;
|
||||||
|
|
@ -69,38 +80,34 @@
|
||||||
.bcnrbar .bopen { margin-left: auto; padding: 4px 11px; border-radius: 7px; border: 1px solid #d6ff3d55;
|
.bcnrbar .bopen { margin-left: auto; padding: 4px 11px; border-radius: 7px; border: 1px solid #d6ff3d55;
|
||||||
background: rgba(214,255,61,.12); color: #eaffb0; cursor: pointer; font-size: 11.5px; }
|
background: rgba(214,255,61,.12); color: #eaffb0; cursor: pointer; font-size: 11.5px; }
|
||||||
.bcnrbar .bopen:hover { background: rgba(214,255,61,.22); }
|
.bcnrbar .bopen:hover { background: rgba(214,255,61,.22); }
|
||||||
.bcnrbar .bdismiss { color: #8b98a9; text-decoration: none; }
|
.bcnrbar .bdismiss { color: #8b98a9; text-decoration: none; } .bcnrbar .bdismiss:hover { color: #fff; }
|
||||||
.bcnrbar .bdismiss:hover { color: #fff; }
|
|
||||||
</style></head>
|
</style></head>
|
||||||
<body>
|
<body>
|
||||||
<div class="tabs" id="tabs"></div>
|
<div class="tabs" id="tabs"></div>
|
||||||
<div class="bar">
|
<div class="bar">
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<button class="ic" id="back" title="Back">
|
<button class="ic" id="back" title="Back"><svg viewBox="0 0 16 16"><path d="M10 3 L5 8 L10 13"/></svg></button>
|
||||||
<svg viewBox="0 0 16 16"><path d="M10 3 L5 8 L10 13"/></svg></button>
|
<button class="ic" id="fwd" title="Forward"><svg viewBox="0 0 16 16"><path d="M6 3 L11 8 L6 13"/></svg></button>
|
||||||
<button class="ic" id="fwd" title="Forward">
|
<button class="ic" id="reload" title="Reload"><svg viewBox="0 0 16 16"><path d="M13 8 a5 5 0 1 1 -1.5 -3.6"/><path d="M13 2.5 L13 5 L10.5 5"/></svg></button>
|
||||||
<svg viewBox="0 0 16 16"><path d="M6 3 L11 8 L6 13"/></svg></button>
|
<button class="ic" id="home" title="Home"><svg viewBox="0 0 16 16"><path d="M2.5 7.5 L8 3 L13.5 7.5"/><path d="M4 6.7 L4 13 L12 13 L12 6.7"/></svg></button>
|
||||||
<button class="ic" id="reload" title="Reload">
|
|
||||||
<svg viewBox="0 0 16 16"><path d="M13 8 a5 5 0 1 1 -1.5 -3.6"/><path d="M13 2.5 L13 5 L10.5 5"/></svg></button>
|
|
||||||
<button class="ic" id="home" title="Home">
|
|
||||||
<svg viewBox="0 0 16 16"><path d="M2.5 7.5 L8 3 L13.5 7.5"/><path d="M4 6.7 L4 13 L12 13 L12 6.7"/></svg></button>
|
|
||||||
</div>
|
</div>
|
||||||
<button class="logo" id="logo" title="Site information">
|
|
||||||
<span>⛓ Theseus</span><span class="chev">▼</span></button>
|
|
||||||
<div class="urlwrap">
|
<div class="urlwrap">
|
||||||
|
<button class="secbadge" id="secbadge" title="Site information" hidden>
|
||||||
|
<svg viewBox="0 0 16 16" aria-hidden="true"><path class="shk" d="M5 7 V5.3 a3 3 0 0 1 6 0 V7"/><rect class="bdy" x="3.6" y="6.9" width="8.8" height="6" rx="1.2"/></svg>
|
||||||
|
</button>
|
||||||
<input id="url" placeholder="Search or enter a .bch / .p2p name or web address" spellcheck="false">
|
<input id="url" placeholder="Search or enter a .bch / .p2p name or web address" spellcheck="false">
|
||||||
<span class="reg" id="reg" hidden></span>
|
<span class="reg" id="reg" hidden></span>
|
||||||
|
<button class="star" id="star" title="Save this page">☆</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="sep"></span>
|
|
||||||
<div class="searchbox">
|
<div class="searchbox">
|
||||||
<span class="mag">🔍</span>
|
<span class="mag">🔍</span>
|
||||||
<input id="search" placeholder="Search the web" spellcheck="false">
|
<input id="search" placeholder="Search" spellcheck="false">
|
||||||
|
<select id="engine" class="engine" title="Search engine"></select>
|
||||||
</div>
|
</div>
|
||||||
<button class="tor" id="tor" title="Route traffic through Tor">🧅 Tor: Off</button>
|
<button class="tor" id="tor" title="Route traffic through Tor">🧅 Tor: Off</button>
|
||||||
<button class="ic" id="settings" title="Settings">
|
<button class="logo" id="logo" title="Settings"><span>⛓ Theseus</span><span class="gear">⚙</span></button>
|
||||||
<svg viewBox="0 0 16 16"><circle cx="8" cy="8" r="2.2"/><path d="M8 1.5 L8 3 M8 13 L8 14.5 M1.5 8 L3 8 M13 8 L14.5 8 M3.3 3.3 L4.3 4.3 M11.7 11.7 L12.7 12.7 M12.7 3.3 L11.7 4.3 M4.3 11.7 L3.3 12.7"/></svg></button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="siteinfo" class="siteinfo" hidden></div>
|
<div class="bookmarks" id="bookmarks" hidden></div>
|
||||||
<div id="tordisc" class="tordisc" hidden>
|
<div id="tordisc" class="tordisc" hidden>
|
||||||
Onion mode hides your IP from sites and your ISP (including .bch lookups).
|
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.
|
It is <b>not full anonymity</b> — this browser can still be fingerprinted; for that use the Tor Browser.
|
||||||
|
|
@ -115,8 +122,9 @@
|
||||||
<script>
|
<script>
|
||||||
const $ = (id) => document.getElementById(id);
|
const $ = (id) => document.getElementById(id);
|
||||||
const T = window.theseus;
|
const T = window.theseus;
|
||||||
// Keep the chrome view tall enough for whatever bars are visible (site info / Tor / BCNR).
|
|
||||||
function syncHeight() { requestAnimationFrame(() => T.setChromeHeight(document.body.scrollHeight)); }
|
function syncHeight() { requestAnimationFrame(() => T.setChromeHeight(document.body.scrollHeight)); }
|
||||||
|
|
||||||
|
// ---- navigation ----
|
||||||
function goURL() { const v = $("url").value.trim(); if (v) T.navigate(v); }
|
function goURL() { const v = $("url").value.trim(); if (v) T.navigate(v); }
|
||||||
$("url").addEventListener("keydown", (e) => { if (e.key === "Enter") goURL(); });
|
$("url").addEventListener("keydown", (e) => { if (e.key === "Enter") goURL(); });
|
||||||
$("search").addEventListener("keydown", (e) => { if (e.key === "Enter") { const q = $("search").value.trim(); if (q) { T.search(q); $("search").value = ""; } } });
|
$("search").addEventListener("keydown", (e) => { if (e.key === "Enter") { const q = $("search").value.trim(); if (q) { T.search(q); $("search").value = ""; } } });
|
||||||
|
|
@ -124,59 +132,76 @@
|
||||||
$("fwd").onclick = () => T.forward();
|
$("fwd").onclick = () => T.forward();
|
||||||
$("reload").onclick = () => T.reload();
|
$("reload").onclick = () => T.reload();
|
||||||
$("home").onclick = () => T.goHome();
|
$("home").onclick = () => T.goHome();
|
||||||
$("settings").onclick = () => T.openSettings();
|
$("logo").onclick = () => T.openSettings(); // logo (right) opens Settings
|
||||||
|
|
||||||
// ---- site-info panel (toggled by the Theseus logo) ----
|
// ---- search engine picker ----
|
||||||
let lastNav = null, infoOpen = false;
|
T.getSearchEngines().then((d) => {
|
||||||
function renderInfo() {
|
const sel = $("engine");
|
||||||
const d = lastNav, box = $("siteinfo");
|
sel.innerHTML = d.engines.map((e) => `<option value="${e.id}">${e.name}</option>`).join("");
|
||||||
const row = (k, v) => `<span class="k">${k}</span><span class="v">${v}</span>`;
|
sel.value = d.current;
|
||||||
let html = "";
|
sel.onchange = () => T.setSearchEngine(sel.value);
|
||||||
if (!d || d.kind === "home") {
|
});
|
||||||
html = row("Page", "Theseus home") +
|
|
||||||
row("About", "Theseus is a decentralized-web navigator. It resolves BCNR names (.bch, .p2p, .nav…) straight from the Bitcoin Cash chain — no ICANN, no DNS middleman.");
|
// ---- bookmarks bar ----
|
||||||
} else if (d.kind === "web") {
|
let current = { url: "", title: "" };
|
||||||
html = row("Site", `<span class="dotv web"></span><code>${(d.host||"").replace(/</g,"<")}</code>`) +
|
let bookmarks = [];
|
||||||
row("Resolved via", "ICANN DNS (ordinary web)") +
|
function renderBookmarks() {
|
||||||
row("Registry", "ICANN — not a BCNR name");
|
const box = $("bookmarks");
|
||||||
} else if (d.kind === "resolving") {
|
if (!bookmarks.length) { box.innerHTML = `<span class="bm-empty">No saved pages yet — click ☆ to save the current page</span>`; syncHeight(); return; }
|
||||||
html = row("Site", `<code>${(d.host||"").replace(/</g,"<")}</code>`) + row("Status", "resolving on the BCH chain…");
|
box.innerHTML = bookmarks.map((b, i) =>
|
||||||
} else if (d.kind === "nxdomain") {
|
`<div class="bm" data-i="${i}" title="${(b.url || "").replace(/"/g, """)}">
|
||||||
html = row("Name", `<span class="dotv err"></span><code>${(d.host||"").replace(/</g,"<")}</code>`) +
|
<span class="bt">${(b.title || b.url).replace(/</g, "<")}</span>
|
||||||
row("Status", "not registered on the BCNR chain (NXDOMAIN)");
|
<span class="bx" data-x="${i}">✕</span></div>`).join("");
|
||||||
} else if (d.kind === "error") {
|
box.querySelectorAll(".bm").forEach((el) => el.onclick = (e) => {
|
||||||
html = row("Status", `<span class="dotv err"></span>${(d.error||"resolution failed").replace(/</g,"<")}`);
|
const i = Number(el.dataset.i);
|
||||||
} else { // ok
|
if (e.target.dataset.x !== undefined) { T.removeBookmark(bookmarks[i].url); }
|
||||||
const cls = (d.source||"").includes("chain") ? "chain" : (d.source||"").includes("Sia") ? "sia" : (d.source||"").includes("server") ? "server" : "web";
|
else T.navigate(bookmarks[i].url);
|
||||||
html = row("Site", `<span class="dotv ${cls}"></span><code>${(d.host||"").replace(/</g,"<")}</code>`) +
|
});
|
||||||
row("Served from", d.source || "chain") +
|
|
||||||
row("Registry", (d.registry || "BCNR") + (d.tld ? " · ." + d.tld : "")) +
|
|
||||||
(d.records && d.records.length ? row("Records", `<code>${d.records.join(", ")}</code>`) : "") +
|
|
||||||
(d.category ? row("Certificate", `<code>${String(d.category).slice(0,24)}…</code>`) : "");
|
|
||||||
}
|
|
||||||
box.innerHTML = html;
|
|
||||||
}
|
|
||||||
function toggleInfo(force) {
|
|
||||||
infoOpen = force !== undefined ? force : !infoOpen;
|
|
||||||
$("siteinfo").hidden = !infoOpen;
|
|
||||||
$("logo").classList.toggle("open", infoOpen);
|
|
||||||
if (infoOpen) renderInfo();
|
|
||||||
syncHeight();
|
syncHeight();
|
||||||
|
updateStar();
|
||||||
}
|
}
|
||||||
$("logo").onclick = () => toggleInfo();
|
function updateStar() {
|
||||||
|
const saved = bookmarks.some((b) => b.url === current.url && current.url);
|
||||||
|
const s = $("star"); s.textContent = saved ? "★" : "☆"; s.classList.toggle("on", saved);
|
||||||
|
s.title = saved ? "Remove from saved pages" : "Save this page";
|
||||||
|
}
|
||||||
|
$("star").onclick = () => {
|
||||||
|
if (!current.url) return;
|
||||||
|
if (bookmarks.some((b) => b.url === current.url)) T.removeBookmark(current.url);
|
||||||
|
else T.addBookmark({ title: current.title || current.url, url: current.url });
|
||||||
|
};
|
||||||
|
T.getBookmarks().then((b) => { bookmarks = b || []; renderBookmarks(); });
|
||||||
|
T.onBookmarks((b) => { bookmarks = b || []; renderBookmarks(); });
|
||||||
|
|
||||||
|
// ---- security badge (Firefox-style padlock); opens the floating overlay ----
|
||||||
|
function setBadge(d) {
|
||||||
|
const b = $("secbadge");
|
||||||
|
if (!d || d.kind === "home") { b.hidden = true; return; }
|
||||||
|
b.hidden = false;
|
||||||
|
let cls = "secbadge", title = "Site information — click for details";
|
||||||
|
if (d.kind === "ok") title = "Secure · " + (d.source || "BCNR") + " — click for details";
|
||||||
|
else if (d.kind === "web") title = "Connection — click for details";
|
||||||
|
else if (d.kind === "resolving") title = "Resolving…";
|
||||||
|
else if (d.kind === "nxdomain") { cls = "secbadge warn"; title = "Not registered on BCNR"; }
|
||||||
|
else if (d.kind === "error") { cls = "secbadge warn"; title = "Resolution error"; }
|
||||||
|
b.className = cls; b.title = title;
|
||||||
|
}
|
||||||
|
// The popover is a separate overlay view; hand main the badge's screen position.
|
||||||
|
$("secbadge").onclick = () => {
|
||||||
|
const r = $("secbadge").getBoundingClientRect();
|
||||||
|
T.toggleSiteInfo({ x: Math.round(r.left), y: Math.round(r.bottom + 4) });
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- Tor ----
|
||||||
let torShown = false;
|
let torShown = false;
|
||||||
$("tor").onclick = () => { T.toggleTor(); if (!torShown) { $("tordisc").hidden = false; torShown = true; syncHeight(); } };
|
$("tor").onclick = () => { T.toggleTor(); if (!torShown) { $("tordisc").hidden = false; torShown = true; syncHeight(); } };
|
||||||
$("tordismiss").onclick = (e) => { e.preventDefault(); $("tordisc").hidden = true; syncHeight(); };
|
$("tordismiss").onclick = (e) => { e.preventDefault(); $("tordisc").hidden = true; syncHeight(); };
|
||||||
|
|
||||||
// Passive "also on BCNR" bar for dual-use ICANN TLDs (.de/.dev/.ltd).
|
// ---- passive BCNR offer ----
|
||||||
T.onBcnrOffer((d) => {
|
T.onBcnrOffer((d) => {
|
||||||
const bar = $("bcnrbar");
|
const bar = $("bcnrbar");
|
||||||
if (d && d.host) {
|
if (d && d.host) { $("bcnrhost").textContent = d.host; $("bcnrreg").textContent = d.registry || "BCNR"; bar.hidden = false; }
|
||||||
$("bcnrhost").textContent = d.host;
|
else bar.hidden = true;
|
||||||
$("bcnrreg").textContent = d.registry || "BCNR";
|
|
||||||
bar.hidden = false;
|
|
||||||
} else { bar.hidden = true; }
|
|
||||||
syncHeight();
|
syncHeight();
|
||||||
});
|
});
|
||||||
$("bcnropen").onclick = () => { $("bcnrbar").hidden = true; syncHeight(); T.switchToBcnr(); };
|
$("bcnropen").onclick = () => { $("bcnrbar").hidden = true; syncHeight(); T.switchToBcnr(); };
|
||||||
|
|
@ -186,9 +211,14 @@
|
||||||
b.textContent = d.state === "on" ? "🧅 Tor: On" : d.state === "connecting" ? "🧅 connecting…" : "🧅 Tor: Off";
|
b.textContent = d.state === "on" ? "🧅 Tor: On" : d.state === "connecting" ? "🧅 connecting…" : "🧅 Tor: Off";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- tabs ----
|
||||||
T.onTabs((d) => {
|
T.onTabs((d) => {
|
||||||
$("back").disabled = !d.canBack; $("fwd").disabled = !d.canForward;
|
$("back").disabled = !d.canBack; $("fwd").disabled = !d.canForward;
|
||||||
if (document.activeElement !== $("url")) $("url").value = d.url || "";
|
if (document.activeElement !== $("url")) $("url").value = d.url || "";
|
||||||
|
current.url = d.url || "";
|
||||||
|
const active = d.tabs.find((t) => t.active);
|
||||||
|
current.title = active ? active.title : "";
|
||||||
|
updateStar();
|
||||||
const box = $("tabs");
|
const box = $("tabs");
|
||||||
box.innerHTML = d.tabs.map((t) =>
|
box.innerHTML = d.tabs.map((t) =>
|
||||||
`<div class="tab ${t.active ? "active" : ""}" data-id="${t.id}">
|
`<div class="tab ${t.active ? "active" : ""}" data-id="${t.id}">
|
||||||
|
|
@ -202,18 +232,19 @@
|
||||||
$("newtab").onclick = () => T.newTab();
|
$("newtab").onclick = () => T.newTab();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- provenance ----
|
||||||
T.onNav((d) => {
|
T.onNav((d) => {
|
||||||
lastNav = d;
|
setBadge(d);
|
||||||
// Registry/TLD badge: shown for any BNS name, painted here (trusted chrome).
|
|
||||||
const reg = $("reg");
|
const reg = $("reg");
|
||||||
if (d.tld) { reg.hidden = false; reg.textContent = (d.registry || "BCNR") + " · ." + d.tld; }
|
if (d.tld) { reg.hidden = false; reg.textContent = (d.registry || "BCNR") + " · ." + d.tld; } else reg.hidden = true;
|
||||||
else reg.hidden = true;
|
|
||||||
// Keep the address bar in sync (unless the user is typing).
|
|
||||||
if (document.activeElement !== $("url")) {
|
if (document.activeElement !== $("url")) {
|
||||||
if (d.kind === "home") $("url").value = "";
|
if (d.kind === "home") $("url").value = "";
|
||||||
else if (d.host && !$("url").value) $("url").value = d.host;
|
else if (d.host && !$("url").value) $("url").value = d.host;
|
||||||
}
|
}
|
||||||
if (infoOpen) renderInfo();
|
// Favorites bar shows only on the new-tab / home page (Edge-style); it hides
|
||||||
|
// once a real page loads so it never eats space while browsing.
|
||||||
|
$("bookmarks").hidden = !(!d || d.kind === "home");
|
||||||
|
syncHeight();
|
||||||
});
|
});
|
||||||
|
|
||||||
syncHeight();
|
syncHeight();
|
||||||
|
|
|
||||||
78
main.js
78
main.js
|
|
@ -21,7 +21,15 @@ const RES_DIR = app.isPackaged ? process.resourcesPath : __dirname;
|
||||||
const RESOLVER = app.isPackaged
|
const RESOLVER = app.isPackaged
|
||||||
? path.join(RES_DIR, "resolver-web.mjs")
|
? path.join(RES_DIR, "resolver-web.mjs")
|
||||||
: path.join(__dirname, "..", "Argus", "src", "lib", "resolver-web.js");
|
: path.join(__dirname, "..", "Argus", "src", "lib", "resolver-web.js");
|
||||||
const SEARCH = (q) => "https://duckduckgo.com/?q=" + encodeURIComponent(q);
|
// Search engines the user can pick from (default persisted in settings.searchEngine).
|
||||||
|
const SEARCH_ENGINES = {
|
||||||
|
duckduckgo: { name: "DuckDuckGo", url: (q) => "https://duckduckgo.com/?q=" + encodeURIComponent(q) },
|
||||||
|
google: { name: "Google", url: (q) => "https://www.google.com/search?q=" + encodeURIComponent(q) },
|
||||||
|
bing: { name: "Bing", url: (q) => "https://www.bing.com/search?q=" + encodeURIComponent(q) },
|
||||||
|
brave: { name: "Brave", url: (q) => "https://search.brave.com/search?q=" + encodeURIComponent(q) },
|
||||||
|
startpage: { name: "Startpage", url: (q) => "https://www.startpage.com/sp/search?query=" + encodeURIComponent(q) },
|
||||||
|
};
|
||||||
|
const SEARCH = (q) => (SEARCH_ENGINES[settings.searchEngine] || SEARCH_ENGINES.duckduckgo).url(q);
|
||||||
// Public content relay (secret-free): serves s3/ip/h/u without shipping keys.
|
// Public content relay (secret-free): serves s3/ip/h/u without shipping keys.
|
||||||
const GATEWAY = "https://navigate.st";
|
const GATEWAY = "https://navigate.st";
|
||||||
|
|
||||||
|
|
@ -71,6 +79,7 @@ const SETTINGS_DEFAULTS = {
|
||||||
timezoneValue: "UTC", // IANA zone used when timezoneMode === "change"
|
timezoneValue: "UTC", // IANA zone used when timezoneMode === "change"
|
||||||
languageMode: "show", // show (real) | hide (en-US) | change (languageValue)
|
languageMode: "show", // show (real) | hide (en-US) | change (languageValue)
|
||||||
languageValue: "en-US", // locale used when languageMode === "change"
|
languageValue: "en-US", // locale used when languageMode === "change"
|
||||||
|
searchEngine: "duckduckgo",// default search engine (see SEARCH_ENGINES)
|
||||||
};
|
};
|
||||||
let settings = { ...SETTINGS_DEFAULTS };
|
let settings = { ...SETTINGS_DEFAULTS };
|
||||||
const settingsFile = () => path.join(app.getPath("userData"), "settings.json");
|
const settingsFile = () => path.join(app.getPath("userData"), "settings.json");
|
||||||
|
|
@ -81,6 +90,12 @@ function loadSettings() {
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
try { fs.writeFileSync(settingsFile(), JSON.stringify(settings, null, 2)); } catch (e) { console.error("settings save failed:", e.message); }
|
try { fs.writeFileSync(settingsFile(), JSON.stringify(settings, null, 2)); } catch (e) { console.error("settings save failed:", e.message); }
|
||||||
}
|
}
|
||||||
|
// ---- bookmarks / saved pages (userData/bookmarks.json) ----
|
||||||
|
let bookmarks = [];
|
||||||
|
const bookmarksFile = () => path.join(app.getPath("userData"), "bookmarks.json");
|
||||||
|
function loadBookmarks() { try { if (fs.existsSync(bookmarksFile())) bookmarks = JSON.parse(fs.readFileSync(bookmarksFile(), "utf8")); } catch (e) { console.error("bookmarks load failed:", e.message); } }
|
||||||
|
function saveBookmarks() { try { fs.writeFileSync(bookmarksFile(), JSON.stringify(bookmarks, null, 2)); } catch (e) { console.error("bookmarks save failed:", e.message); } }
|
||||||
|
function emitBookmarks() { try { chrome?.webContents.send("bookmarks", bookmarks); } catch {} }
|
||||||
// WebRTC IP-handling policy: protect by default (independent of Tor), strongest under Tor.
|
// WebRTC IP-handling policy: protect by default (independent of Tor), strongest under Tor.
|
||||||
function webrtcPolicy() {
|
function webrtcPolicy() {
|
||||||
if (!settings.webrtcProtect) return "default";
|
if (!settings.webrtcProtect) return "default";
|
||||||
|
|
@ -340,8 +355,12 @@ async function serveBns(request) {
|
||||||
|
|
||||||
// ---- window + tabs ----
|
// ---- window + tabs ----
|
||||||
let win, chrome, statusbar;
|
let win, chrome, statusbar;
|
||||||
let CHROME_H = 84; // grows when an extra bar (Tor notice / BCNR offer / site info) is shown
|
let CHROME_H = 84; // grows when an extra bar (Tor notice / BCNR offer) is shown
|
||||||
const STATUS_H = 24; // fixed bottom resolver/provenance line
|
const STATUS_H = 24; // fixed bottom resolver/provenance line
|
||||||
|
// Site-info popover: a floating overlay VIEW on top of the page content, so it
|
||||||
|
// never pushes the page down. Positioned under the address-bar badge on demand.
|
||||||
|
let popover, popVisible = false, popPos = { x: 8, y: 90 };
|
||||||
|
const POP_W = 360, POP_H = 200;
|
||||||
const tabs = []; // { id, view, title, url, prov }
|
const tabs = []; // { id, view, title, url, prov }
|
||||||
let activeId = null, tabSeq = 0;
|
let activeId = null, tabSeq = 0;
|
||||||
const tabById = (id) => tabs.find((t) => t.id === id);
|
const tabById = (id) => tabs.find((t) => t.id === id);
|
||||||
|
|
@ -352,6 +371,7 @@ const activeTab = () => tabById(activeId);
|
||||||
function pushNav(prov) {
|
function pushNav(prov) {
|
||||||
chrome?.webContents.send("nav", prov);
|
chrome?.webContents.send("nav", prov);
|
||||||
statusbar?.webContents.send("nav", prov);
|
statusbar?.webContents.send("nav", prov);
|
||||||
|
if (popVisible) popover?.webContents.send("site-info", prov);
|
||||||
}
|
}
|
||||||
|
|
||||||
function layout() {
|
function layout() {
|
||||||
|
|
@ -361,9 +381,28 @@ function layout() {
|
||||||
const bodyH = Math.max(0, height - CHROME_H - STATUS_H);
|
const bodyH = Math.max(0, height - CHROME_H - STATUS_H);
|
||||||
for (const t of tabs) t.view.setBounds({ x: 0, y: CHROME_H, width, height: bodyH });
|
for (const t of tabs) t.view.setBounds({ x: 0, y: CHROME_H, width, height: bodyH });
|
||||||
statusbar?.setBounds({ x: 0, y: height - STATUS_H, width, height: STATUS_H });
|
statusbar?.setBounds({ x: 0, y: height - STATUS_H, width, height: STATUS_H });
|
||||||
|
positionPopover();
|
||||||
|
}
|
||||||
|
function positionPopover() {
|
||||||
|
if (!popover) return;
|
||||||
|
const { width } = win.getContentBounds();
|
||||||
|
const x = Math.max(6, Math.min(popPos.x, width - POP_W - 6));
|
||||||
|
popover.setBounds({ x, y: popPos.y, width: POP_W, height: POP_H });
|
||||||
|
}
|
||||||
|
function showPopover(show) {
|
||||||
|
if (!popover) return;
|
||||||
|
if (show) {
|
||||||
|
positionPopover();
|
||||||
|
// Re-add to the top of the z-order (tabs added later would otherwise cover it).
|
||||||
|
win.contentView.removeChildView(popover);
|
||||||
|
win.contentView.addChildView(popover);
|
||||||
|
popover.setVisible(true); popVisible = true;
|
||||||
|
popover.webContents.send("site-info", activeTab()?.prov || { kind: "home" });
|
||||||
|
} else { popover.setVisible(false); popVisible = false; }
|
||||||
}
|
}
|
||||||
function setActive(id) {
|
function setActive(id) {
|
||||||
activeId = id;
|
activeId = id;
|
||||||
|
if (popVisible) showPopover(false); // don't carry a stale popover across tabs
|
||||||
for (const t of tabs) t.view.setVisible(t.id === id);
|
for (const t of tabs) t.view.setVisible(t.id === id);
|
||||||
const t = activeTab();
|
const t = activeTab();
|
||||||
if (t?.prov) pushNav(t.prov);
|
if (t?.prov) pushNav(t.prov);
|
||||||
|
|
@ -466,6 +505,12 @@ function createWindow() {
|
||||||
statusbar.webContents.loadFile("statusbar.html");
|
statusbar.webContents.loadFile("statusbar.html");
|
||||||
// The bottom bar loads async; once ready, paint it with the active tab's state.
|
// The bottom bar loads async; once ready, paint it with the active tab's state.
|
||||||
statusbar.webContents.once("did-finish-load", () => { const t = activeTab(); if (t?.prov) statusbar.webContents.send("nav", t.prov); });
|
statusbar.webContents.once("did-finish-load", () => { const t = activeTab(); if (t?.prov) statusbar.webContents.send("nav", t.prov); });
|
||||||
|
// Floating site-info overlay (hidden until the address-bar badge is clicked).
|
||||||
|
popover = new WebContentsView({ webPreferences: { preload: path.join(__dirname, "popover-preload.js") } });
|
||||||
|
try { popover.setBackgroundColor("#00000000"); } catch {}
|
||||||
|
win.contentView.addChildView(popover);
|
||||||
|
popover.webContents.loadFile("popover.html");
|
||||||
|
popover.setVisible(false);
|
||||||
chrome.webContents.once("did-finish-load", () => {
|
chrome.webContents.once("did-finish-load", () => {
|
||||||
const saved = settings.restoreSession ? loadSession() : [];
|
const saved = settings.restoreSession ? loadSession() : [];
|
||||||
if (saved.length) saved.forEach((u) => createTab(u)); else createTab();
|
if (saved.length) saved.forEach((u) => createTab(u)); else createTab();
|
||||||
|
|
@ -548,6 +593,33 @@ ipcMain.handle("go-forward", () => { const wc = activeTab()?.view.webContents; i
|
||||||
ipcMain.handle("reload", () => activeTab()?.view.webContents.reload());
|
ipcMain.handle("reload", () => activeTab()?.view.webContents.reload());
|
||||||
ipcMain.handle("toggle-tor", () => { torState === "off" ? startTor() : stopTor(); });
|
ipcMain.handle("toggle-tor", () => { torState === "off" ? startTor() : stopTor(); });
|
||||||
ipcMain.handle("open-settings", () => { const ex = tabs.find((t) => t.settings); if (ex) return setActive(ex.id); createTab(null, { settings: true }); });
|
ipcMain.handle("open-settings", () => { const ex = tabs.find((t) => t.settings); if (ex) return setActive(ex.id); createTab(null, { settings: true }); });
|
||||||
|
ipcMain.handle("toggle-site-info", (_e, rect) => {
|
||||||
|
if (popVisible) return showPopover(false);
|
||||||
|
if (rect) popPos = { x: Math.round(rect.x), y: Math.round(rect.y) };
|
||||||
|
showPopover(true);
|
||||||
|
});
|
||||||
|
ipcMain.handle("close-site-info", () => showPopover(false));
|
||||||
|
ipcMain.handle("search-engines", () => ({
|
||||||
|
engines: Object.entries(SEARCH_ENGINES).map(([id, e]) => ({ id, name: e.name })),
|
||||||
|
current: settings.searchEngine,
|
||||||
|
}));
|
||||||
|
ipcMain.handle("set-search-engine", (_e, id) => {
|
||||||
|
if (SEARCH_ENGINES[id]) { settings.searchEngine = id; saveSettings(); }
|
||||||
|
return settings.searchEngine;
|
||||||
|
});
|
||||||
|
ipcMain.handle("bookmarks-get", () => bookmarks);
|
||||||
|
ipcMain.handle("bookmark-add", (_e, bm) => {
|
||||||
|
if (bm && bm.url && !bookmarks.some((b) => b.url === bm.url)) {
|
||||||
|
bookmarks.push({ title: bm.title || bm.url, url: bm.url });
|
||||||
|
saveBookmarks(); emitBookmarks();
|
||||||
|
}
|
||||||
|
return bookmarks;
|
||||||
|
});
|
||||||
|
ipcMain.handle("bookmark-remove", (_e, url) => {
|
||||||
|
bookmarks = bookmarks.filter((b) => b.url !== url);
|
||||||
|
saveBookmarks(); emitBookmarks();
|
||||||
|
return bookmarks;
|
||||||
|
});
|
||||||
ipcMain.handle("settings-get", () => settings);
|
ipcMain.handle("settings-get", () => settings);
|
||||||
ipcMain.handle("settings-set", (_e, key, val) => {
|
ipcMain.handle("settings-set", (_e, key, val) => {
|
||||||
if (key in SETTINGS_DEFAULTS) { settings[key] = val; saveSettings(); }
|
if (key in SETTINGS_DEFAULTS) { settings[key] = val; saveSettings(); }
|
||||||
|
|
@ -572,7 +644,9 @@ ipcMain.handle("switch-to-bcnr", () => {
|
||||||
// launching the full UI (see dev/selftest.js). Normal `npm start` is unchanged.
|
// launching the full UI (see dev/selftest.js). Normal `npm start` is unchanged.
|
||||||
if (!process.env.THESEUS_NO_AUTOSTART) {
|
if (!process.env.THESEUS_NO_AUTOSTART) {
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
|
Menu.setApplicationMenu(null); // drop the native File/Edit/View/Help menu bar
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
loadBookmarks();
|
||||||
applyPermissions();
|
applyPermissions();
|
||||||
applyAcceptLanguage();
|
applyAcceptLanguage();
|
||||||
protocol.handle("bns", serveBns);
|
protocol.handle("bns", serveBns);
|
||||||
|
|
|
||||||
5
popover-preload.js
Normal file
5
popover-preload.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
const { contextBridge, ipcRenderer } = require("electron");
|
||||||
|
contextBridge.exposeInMainWorld("pop", {
|
||||||
|
onData: (cb) => ipcRenderer.on("site-info", (_e, d) => cb(d)),
|
||||||
|
close: () => ipcRenderer.invoke("close-site-info"),
|
||||||
|
});
|
||||||
47
popover.html
Normal file
47
popover.html
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html><head><meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
:root { color-scheme: dark; font-family: system-ui, sans-serif; }
|
||||||
|
html, body { margin: 0; height: 100%; background: transparent; }
|
||||||
|
.card { height: 100%; display: flex; flex-direction: column; background: #141b28;
|
||||||
|
border: 1px solid #ffffff24; border-radius: 10px; box-shadow: 0 12px 34px #000b; overflow: hidden; }
|
||||||
|
.head { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #ffffff12; font-size: 12px; color: #b9c2d0; }
|
||||||
|
.head .x { border: none; background: transparent; color: #8b98a9; cursor: pointer; font-size: 13px; border-radius: 5px; padding: 1px 6px; }
|
||||||
|
.head .x:hover { background: #ffffff14; color: #fff; }
|
||||||
|
.body { display: grid; grid-template-columns: auto 1fr; gap: 6px 14px; padding: 11px 14px; font-size: 12px; overflow-y: auto; }
|
||||||
|
.k { color: #7f8aa0; } .v { color: #e7eaf1; }
|
||||||
|
.v code { font-family: ui-monospace, monospace; color: #bfeae4; word-break: break-all; }
|
||||||
|
.dotv { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #5e6678; margin-right: 6px; }
|
||||||
|
.dotv.chain { background: #d6ff3d; } .dotv.sia { background: #b39ddb; }
|
||||||
|
.dotv.server { background: #4fd1a5; } .dotv.web { background: #8b93a7; } .dotv.err { background: #f6768a; }
|
||||||
|
</style></head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<div class="head"><span>Site information</span><button class="x" id="x" title="Close">✕</button></div>
|
||||||
|
<div class="body" id="body"></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const $ = (id) => document.getElementById(id);
|
||||||
|
$("x").onclick = () => window.pop.close();
|
||||||
|
const esc = (s) => String(s || "").replace(/</g, "<");
|
||||||
|
const row = (k, v) => `<span class="k">${k}</span><span class="v">${v}</span>`;
|
||||||
|
window.pop.onData((d) => {
|
||||||
|
let html = "";
|
||||||
|
if (!d || d.kind === "home") html = row("Page", "Theseus home");
|
||||||
|
else if (d.kind === "web") html = row("Site", `<span class="dotv web"></span><code>${esc(d.host)}</code>`) + row("Resolved via", "ICANN DNS (web)") + row("Registry", "ICANN");
|
||||||
|
else if (d.kind === "resolving") html = row("Site", `<code>${esc(d.host)}</code>`) + row("Status", "resolving on the BCH chain…");
|
||||||
|
else if (d.kind === "nxdomain") html = row("Name", `<span class="dotv err"></span><code>${esc(d.host)}</code>`) + row("Status", "not registered (NXDOMAIN)");
|
||||||
|
else if (d.kind === "error") html = row("Status", `<span class="dotv err"></span>${esc(d.error || "resolution failed")}`);
|
||||||
|
else {
|
||||||
|
const cls = (d.source || "").includes("chain") ? "chain" : (d.source || "").includes("Sia") ? "sia" : (d.source || "").includes("server") ? "server" : "web";
|
||||||
|
html = row("Site", `<span class="dotv ${cls}"></span><code>${esc(d.host)}</code>`) +
|
||||||
|
row("Served from", esc(d.source) || "chain") +
|
||||||
|
row("Registry", (d.registry || "BCNR") + (d.tld ? " · ." + d.tld : "")) +
|
||||||
|
(d.records && d.records.length ? row("Records", `<code>${esc(d.records.join(", "))}</code>`) : "") +
|
||||||
|
(d.category ? row("Certificate", `<code>${esc(String(d.category).slice(0, 24))}…</code>`) : "");
|
||||||
|
}
|
||||||
|
$("body").innerHTML = html;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body></html>
|
||||||
|
|
@ -11,8 +11,15 @@ contextBridge.exposeInMainWorld("theseus", {
|
||||||
reload: () => ipcRenderer.invoke("reload"),
|
reload: () => ipcRenderer.invoke("reload"),
|
||||||
toggleTor: () => ipcRenderer.invoke("toggle-tor"),
|
toggleTor: () => ipcRenderer.invoke("toggle-tor"),
|
||||||
openSettings: () => ipcRenderer.invoke("open-settings"),
|
openSettings: () => ipcRenderer.invoke("open-settings"),
|
||||||
|
toggleSiteInfo: (rect) => ipcRenderer.invoke("toggle-site-info", rect),
|
||||||
switchToBcnr: () => ipcRenderer.invoke("switch-to-bcnr"),
|
switchToBcnr: () => ipcRenderer.invoke("switch-to-bcnr"),
|
||||||
setChromeHeight: (h) => ipcRenderer.invoke("set-chrome-height", h),
|
setChromeHeight: (h) => ipcRenderer.invoke("set-chrome-height", h),
|
||||||
|
getSearchEngines: () => ipcRenderer.invoke("search-engines"),
|
||||||
|
setSearchEngine: (id) => ipcRenderer.invoke("set-search-engine", id),
|
||||||
|
getBookmarks: () => ipcRenderer.invoke("bookmarks-get"),
|
||||||
|
addBookmark: (bm) => ipcRenderer.invoke("bookmark-add", bm),
|
||||||
|
removeBookmark: (url) => ipcRenderer.invoke("bookmark-remove", url),
|
||||||
|
onBookmarks: (cb) => ipcRenderer.on("bookmarks", (_e, d) => cb(d)),
|
||||||
onNav: (cb) => ipcRenderer.on("nav", (_e, d) => cb(d)),
|
onNav: (cb) => ipcRenderer.on("nav", (_e, d) => cb(d)),
|
||||||
onTor: (cb) => ipcRenderer.on("tor", (_e, d) => cb(d)),
|
onTor: (cb) => ipcRenderer.on("tor", (_e, d) => cb(d)),
|
||||||
onTabs: (cb) => ipcRenderer.on("tabs", (_e, d) => cb(d)),
|
onTabs: (cb) => ipcRenderer.on("tabs", (_e, d) => cb(d)),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue