mirror of
https://github.com/samsonjs/spirit-tracker.git
synced 2026-03-25 09:25:51 +00:00
feat: Better URL rendering
This commit is contained in:
parent
2f708a9092
commit
f33ff16419
2 changed files with 35 additions and 71 deletions
|
|
@ -1,4 +1,3 @@
|
|||
/* viz/app/search_page.js */
|
||||
import { esc, renderThumbHtml, prettyTs } from "./dom.js";
|
||||
import { tokenizeQuery, matchesAllTokens, displaySku, keySkuForRow } from "./sku.js";
|
||||
import { loadIndex, loadRecent, loadSavedQuery, saveQuery } from "./state.js";
|
||||
|
|
@ -32,7 +31,7 @@ export function renderSearch($app) {
|
|||
let allAgg = [];
|
||||
let indexReady = false;
|
||||
|
||||
// sku(canonical) -> storeLabel -> url
|
||||
// canonicalSku -> storeLabel -> url
|
||||
let URL_BY_SKU_STORE = new Map();
|
||||
|
||||
function buildUrlMap(listings, canonicalSkuFn) {
|
||||
|
|
@ -60,11 +59,7 @@ export function renderSearch($app) {
|
|||
function urlForAgg(it, storeLabel) {
|
||||
const sku = String(it?.sku || "");
|
||||
const s = String(storeLabel || "");
|
||||
return (
|
||||
URL_BY_SKU_STORE.get(sku)?.get(s) ||
|
||||
String(it?.sampleUrl || "").trim() ||
|
||||
""
|
||||
);
|
||||
return URL_BY_SKU_STORE.get(sku)?.get(s) || "";
|
||||
}
|
||||
|
||||
function renderAggregates(items) {
|
||||
|
|
@ -81,8 +76,8 @@ export function renderSearch($app) {
|
|||
const price = it.cheapestPriceStr ? it.cheapestPriceStr : "(no price)";
|
||||
const store = it.cheapestStoreLabel || ([...it.stores][0] || "Store");
|
||||
|
||||
// link must match displayed store label
|
||||
const href = urlForAgg(it, store);
|
||||
// link must match the displayed store label
|
||||
const href = urlForAgg(it, store) || String(it.sampleUrl || "").trim();
|
||||
const storeBadge = href
|
||||
? `<a class="badge" href="${esc(
|
||||
href
|
||||
|
|
@ -94,18 +89,18 @@ export function renderSearch($app) {
|
|||
return `
|
||||
<div class="item" data-sku="${esc(it.sku)}">
|
||||
<div class="itemRow">
|
||||
<div class="thumbBox">${renderThumbHtml(it.img)}</div>
|
||||
|
||||
<div class="thumbBox">
|
||||
${renderThumbHtml(it.img)}
|
||||
</div>
|
||||
<div class="itemBody">
|
||||
<div class="itemMain">
|
||||
<div class="itemTop">
|
||||
<div class="itemName">${esc(it.name || "(no name)")}</div>
|
||||
</div>
|
||||
|
||||
<div class="itemFacts">
|
||||
<div class="mono priceBig">${esc(price)}</div>
|
||||
${storeBadge}
|
||||
<span class="badge mono">${esc(displaySku(it.sku))}</span>
|
||||
</div>
|
||||
<div class="metaRow">
|
||||
<span class="mono price">${esc(price)}</span>
|
||||
${storeBadge}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -160,10 +155,8 @@ export function renderSearch($app) {
|
|||
: `${esc(r.oldPrice || "")} → ${esc(r.newPrice || "")}`;
|
||||
|
||||
const when = r.ts ? prettyTs(r.ts) : r.date || "";
|
||||
|
||||
const rawSku = String(r.sku || "");
|
||||
const sku = canon(rawSku);
|
||||
|
||||
const img = aggBySku.get(sku)?.img || "";
|
||||
|
||||
const href = String(r.url || "").trim();
|
||||
|
|
@ -175,25 +168,26 @@ export function renderSearch($app) {
|
|||
)}</a>`
|
||||
: `<span class="badge">${esc(r.storeLabel || "")}</span>`;
|
||||
|
||||
// date as a badge so it sits nicely in the single meta row
|
||||
const dateBadge = when ? `<span class="badge mono">${esc(when)}</span>` : "";
|
||||
|
||||
return `
|
||||
<div class="item" data-sku="${esc(sku)}">
|
||||
<div class="itemRow">
|
||||
<div class="thumbBox">${renderThumbHtml(img)}</div>
|
||||
|
||||
<div class="thumbBox">
|
||||
${renderThumbHtml(img)}
|
||||
</div>
|
||||
<div class="itemBody">
|
||||
<div class="itemMain">
|
||||
<div class="itemTop">
|
||||
<div class="itemName">${esc(r.name || "(no name)")}</div>
|
||||
<div class="meta">
|
||||
<span class="badge">${esc(kind)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="itemFacts">
|
||||
<div class="mono priceBig">${esc(priceLine)}</div>
|
||||
${storeBadge}
|
||||
<div class="small mono">${esc(when)}</div>
|
||||
<span class="badge mono">${esc(displaySku(sku))}</span>
|
||||
</div>
|
||||
<div class="metaRow">
|
||||
<span class="badge">${esc(kind)}</span>
|
||||
<span class="mono price">${esc(priceLine)}</span>
|
||||
${storeBadge}
|
||||
${dateBadge}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* viz/style.css */
|
||||
:root {
|
||||
--bg: #0b0d10;
|
||||
--panel: #12161b;
|
||||
|
|
@ -19,7 +18,7 @@ body {
|
|||
a { color: var(--accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
/* badge links should look like badges, but clearly clickable */
|
||||
/* badge links: keep badge look, but clearly clickable */
|
||||
a.badge { color: var(--muted); }
|
||||
a.badge:hover { text-decoration: underline; cursor: pointer; }
|
||||
|
||||
|
|
@ -82,7 +81,7 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
|
|||
.itemRow {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: stretch; /* key: let thumb fill height */
|
||||
align-items: stretch; /* let thumb fill card height */
|
||||
}
|
||||
|
||||
.thumbBox {
|
||||
|
|
@ -95,7 +94,6 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
|
|||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
height: auto;
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
|
|
@ -115,26 +113,6 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
|
|||
.itemBody {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.itemMain {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.itemFacts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.itemTop {
|
||||
|
|
@ -147,6 +125,7 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
|
|||
.itemName {
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.badge {
|
||||
|
|
@ -161,22 +140,23 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
|
|||
gap: 6px;
|
||||
}
|
||||
|
||||
.meta {
|
||||
.metaRow {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
||||
|
||||
.priceBig {
|
||||
font-size: 14px;
|
||||
.price {
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -261,16 +241,6 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
|
|||
.thumbBox { width: 56px; flex: 0 0 56px; min-height: 56px; }
|
||||
.detailThumbBox { width: 84px; height: 84px; flex: 0 0 84px; }
|
||||
|
||||
/* on mobile, keep the "facts" inline so cards don't get super tall */
|
||||
.itemBody { flex-direction: column; gap: 10px; }
|
||||
.itemFacts {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chartBox {
|
||||
height: 58vh;
|
||||
min-height: 260px;
|
||||
|
|
|
|||
Loading…
Reference in a new issue