app
Widget Registry
Public Made by Adomby adom
The reviewed registry of widgets renderable from plain markdown on any wiki page
← Commit history
adom-stock: brand type, even pill row, checked stamp
1 file changed
+12−9
widgets/stock-widget.js+12−9@@ -3,16 +3,19 @@ * src is the page's stock route: /api/pages/OWNER/SLUG/stock */ (() => { const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));- const CSS = `:host{display:block;color:#e6edf3;font:12px/1.3 system-ui,sans-serif;--accent:#00b8b0;--muted:#8b949e;--border:#30363d;--bg:#0d1117;--surface:#161b22}-*{box-sizing:border-box}.wrap{display:flex;flex-wrap:wrap;align-items:center;gap:6px}-.pill{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border:1px solid var(--border);border-radius:999px;background:var(--surface);color:#e6edf3;text-decoration:none;white-space:nowrap;font-variant-numeric:tabular-nums}-a.pill:hover{border-color:var(--accent)}.pill .v{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.04em}.pill .q{color:var(--accent)}.pill .q.out{color:var(--muted)}.pill .p{color:#e6edf3}-.pill.dim{color:var(--muted)}.pill.dim .q{color:var(--muted)}-.when{flex-basis:100%;color:var(--muted);font-size:11px;margin-top:2px}.when a{color:var(--accent);text-decoration:none}.note{color:var(--muted)}`;+ const CSS = `@font-face{font-family:'Satoshi';src:url('https://adom.inc/fonts/satoshi/satoshi-400-normal.woff2') format('woff2');font-weight:400;font-display:swap}+@font-face{font-family:'Satoshi';src:url('https://adom.inc/fonts/satoshi/satoshi-500-normal.woff2') format('woff2');font-weight:500;font-display:swap}+:host{display:block;color:#e6edf3;font:12px/1.3 'Satoshi',system-ui,sans-serif;--accent:#00b8b0;--muted:#8b949e;--border:#30363d;--bg:#0d1117;--surface:#161b22}+*{box-sizing:border-box}.wrap{display:flex;flex-wrap:wrap;gap:5px;align-items:stretch}+.pill{flex:1 1 auto;display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:4px 9px;border:1px solid var(--border);border-radius:999px;background:var(--surface);color:#e6edf3;text-decoration:none;white-space:nowrap;font-size:11.5px;line-height:1.2;font-variant-numeric:tabular-nums;transition:border-color .15s}+a.pill:hover{border-color:var(--accent)}.pill .v{color:var(--muted);font-weight:500}.pill .q{color:var(--accent);font-weight:500}.pill .q.out{color:var(--muted);font-weight:400}.pill .p{color:#e6edf3}+.pill.dim{color:var(--muted)}.pill.dim .q{color:var(--muted);font-weight:400}+.when{flex-basis:100%;color:var(--muted);font-size:11px;line-height:1.2;margin-top:2px}.when a{color:var(--accent);text-decoration:none}.when a:hover{text-decoration:underline}.note{color:var(--muted);font-size:11.5px}`; const VENDORS = ['mouser', 'digikey', 'lcsc']; // Freshness stamp in Ray's form: 12h ago (from the distributor's fetched_at). const ago = iso => { if (!iso) return ''; const m = Math.max(0, (Date.now() - Date.parse(iso)) / 60000); return m < 60 ? `${Math.max(1, Math.round(m))}m ago` : m < 2880 ? `${Math.round(m / 60)}h ago` : `${Math.round(m / 1440)}d ago`; }; const num = n => Number(n).toLocaleString();+ const price = p => String(p).replace(/(\.\d\d)0$/, '$1'); class AdomStock extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); } async connectedCallback() {@@ -55,12 +58,12 @@ a.pill:hover{border-color:var(--accent)}.pill .v{color:var(--muted);font-size:11 if (!r) return `<span class="pill dim"><span class="v">${esc(label)}</span><span class="q">not asked</span></span>`; if (r.error) return `<span class="pill dim" title="${esc(r.error)}"><span class="v">${esc(label)}</span><span class="q">${/no data/.test(r.error) ? 'no data yet' : /budget/.test(r.error) ? 'daily lookups used' : /PARTS_API_KEY/.test(r.error) ? 'not configured' : 'lookup failed'}</span></span>`; if (!r.found) return `<span class="pill dim"><span class="v">${esc(label)}</span><span class="q">not listed</span></span>`;- const inner = `<span class="v">${esc(label)}</span><span class="q ${r.stock > 0 ? '' : 'out'}">${r.stock > 0 ? num(r.stock) : 'out of stock'}</span>${r.unit_price ? `<span class="p">${esc(r.unit_price)}</span>` : ''}`;+ const inner = `<span class="v">${esc(label)}</span><span class="q ${r.stock > 0 ? '' : 'out'}">${r.stock > 0 ? num(r.stock) : 'out of stock'}</span>${r.unit_price ? `<span class="p">${esc(price(r.unit_price))}</span>` : ''}`; const title = `${label}: ${r.part || r.mpn || ''}${r.checked_at ? `, ${ago(r.checked_at)}` : ''}`; return r.product_url ? `<a class="pill" href="${esc(r.product_url)}" target="_blank" rel="noopener" title="${esc(title)}">${inner}</a>` : `<span class="pill" title="${esc(title)}">${inner}</span>`; });- const refresh = d.checked_at && d.signed_in !== false && !d.login_required ? ' <a class="refresh" href="#" title="Ask the distributors again (counts against the daily lookup budget)">refresh</a>' : d.login_required ? ' (sign in to refresh)' : '';- wrap.innerHTML = pills.join('') + `<span class="when">${esc(ago(d.checked_at))}${refresh}</span>`;+ const refresh = d.checked_at && d.signed_in !== false && !d.login_required ? ' ยท <a class="refresh" href="#" title="Ask the distributors again (counts against the daily lookup budget)">Refresh</a>' : d.login_required ? ' (sign in to refresh)' : '';+ wrap.innerHTML = pills.join('') + `<span class="when">${d.checked_at ? 'Checked ' + esc(ago(d.checked_at)) : ''}${refresh}</span>`; } } if (!customElements.get('adom-stock')) customElements.define('adom-stock', AdomStock);