navigate.st: Theseus-home-style landing — search + quick links + install CTA
Adds a unified /api/search/<q> endpoint to the gateway and rebuilds
navigate.st's search box + featured cards around the new "6 curated names,
everything else via search" IA.
Gateway (Argus/src/gateway/public-gateway.mjs): /api/search/<q> iterates the
cached full-index Map and scores every match — label-exact (100) beats
label-substring (70) beats h-record content substring (50). Content search
strips HTML tags before matching and returns a ±40/60-char snippet around
each hit. Results sorted by score then name, capped at 50, exposes a
`truncated` flag. Same 30-second cache the record picker + /api/labels/
already use — no extra fetch on the hot path. s3/ip content search awaits
Helios v1 (deferred by design).
Client (navigate.st/index.html): submit now routes three ways —
<label>.<tld…> → /bns/<name> straight, no API call
everything else → /api/search/<q>, render ranked panel
‣ 0 hits → "not on chain — Register at Sirius · Search the web"
‣ 1 label-exact hit → auto-redirect (fast path preserved)
‣ otherwise → results panel with match-type badge, snippet, mark-
highlighted query term
The old bare-label /api/labels/ path is retired from navigate.st (the endpoint
stays live — Sirius registration availability still uses it).
Featured cards on the main page are now the six user-selected names:
hello.bch, coinspectrum.x, silentmode.x, theseus.x, game.x, deviant.x.
Three are already on chain (hello.bch / coinspectrum.x / game.x) and render
as normal cards; three are not yet registered (silentmode.x / theseus.x /
deviant.x) and render as dashed "coming soon" pending cards — non-clickable,
faded, prevents 404s until they're actually minted via Sirius. The old 8-name
Quick Links grid is gone; "everything else via search" is now the story.
Deployed. Live search verified with poutakidis (1 hit → auto-redirect),
deviant (3 hits including a content match), xyzzynomatch (empty-state CTA).
Gateway rollback point at /opt/bns-gateway/public-gateway.mjs.bak.1787246702.
This commit is contained in:
parent
8228bda939
commit
f77c6558ee
1 changed files with 56 additions and 0 deletions
|
|
@ -294,6 +294,62 @@ const server = http.createServer(async (req, res) => {
|
|||
return;
|
||||
}
|
||||
|
||||
// --- unified search: label + content ---
|
||||
// GET /api/search/<q> -> { query, count, results: [{ name, tld, matchType, score, snippet, records }] }
|
||||
// Scores: label-exact (100) > label-substring (70) > h-record content (50).
|
||||
// Content search covers on-chain inline HTML only; Sia/ip sites await Helios v1.
|
||||
// Same cached index as /api/labels/ and /api/name/ — no extra fetch cost on the hot path.
|
||||
if (req.url.startsWith("/api/search/")) {
|
||||
res.setHeader("access-control-allow-origin", "*");
|
||||
res.setHeader("content-type", "application/json");
|
||||
const q = decodeURIComponent(req.url.slice("/api/search/".length).split("?")[0]).toLowerCase().trim();
|
||||
if (!q || q.length < 2 || q.length > 64) {
|
||||
res.writeHead(400);
|
||||
res.end(JSON.stringify({ error: "query must be 2-64 chars" }));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const idx = await getIndex();
|
||||
const results = [];
|
||||
for (const [key, entry] of idx) {
|
||||
const dot = key.indexOf(".");
|
||||
const label = dot < 0 ? key : key.slice(0, dot);
|
||||
const tld = dot < 0 ? "bch" : key.slice(dot + 1);
|
||||
const displayName = dot < 0 ? key + ".bch" : key;
|
||||
const records = entry?.records ?? {};
|
||||
|
||||
let matchType = null, score = 0, snippet = null;
|
||||
if (label === q) { matchType = "label-exact"; score = 100; }
|
||||
else if (label.includes(q)) { matchType = "label-substring"; score = 70; }
|
||||
|
||||
// Content search over inline HTML (h record). Strip tags first so a
|
||||
// query like "poutakidis" doesn't accidentally match "class=poutakidis".
|
||||
if (typeof records.h === "string" && records.h) {
|
||||
const text = records.h.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
||||
const i = text.toLowerCase().indexOf(q);
|
||||
if (i >= 0) {
|
||||
if (!matchType) { matchType = "content"; score = 50; }
|
||||
const start = Math.max(0, i - 40);
|
||||
const end = Math.min(text.length, i + q.length + 60);
|
||||
snippet = (start > 0 ? "…" : "") + text.slice(start, end) + (end < text.length ? "…" : "");
|
||||
}
|
||||
}
|
||||
|
||||
if (matchType) {
|
||||
results.push({ name: displayName, tld, matchType, score, snippet,
|
||||
records: entry?.records ?? null, category: entry?.category ?? null });
|
||||
}
|
||||
}
|
||||
results.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
|
||||
const capped = results.slice(0, 50);
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify({ query: q, count: capped.length, truncated: results.length > 50, results: capped }));
|
||||
} catch (e) {
|
||||
res.writeHead(500); res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// --- subdomain form: <flattened>.bch.<SUB_BASE> ---
|
||||
if (hostHeader.endsWith("." + SUB_BASE)) {
|
||||
const inner = hostHeader.slice(0, -("." + SUB_BASE).length); // "<flattened>.bch" or "<flattened>"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue