|
3 | 3 | const raw = import.meta.env.BASE_URL; |
4 | 4 | const base = raw.endsWith("/") ? raw : raw + "/"; |
5 | 5 | const absUrl = (path) => new URL(path.replace(/^\//, ""), location.origin + base).href; |
6 | | -const esc = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); |
| 6 | +const esc = (s) => String(s) |
| 7 | + .replace(/&/g, "&") |
| 8 | + .replace(/</g, "<") |
| 9 | + .replace(/>/g, ">") |
| 10 | + .replace(/"/g, """) |
| 11 | + .replace(/'/g, "'"); |
7 | 12 |
|
8 | 13 | /* ---- theme: follow OS preference until the user picks one (persisted) ---- */ |
9 | 14 | const root = document.documentElement; |
@@ -180,6 +185,54 @@ function countUp(node, target) { |
180 | 185 | })(performance.now()); |
181 | 186 | } |
182 | 187 |
|
| 188 | +/* ============================================================ |
| 189 | + HISTORY |
| 190 | + ============================================================ */ |
| 191 | +(function history() { |
| 192 | + const totalEl = document.getElementById("history-total"); |
| 193 | + const countsEl = document.getElementById("history-counts"); |
| 194 | + const listEl = document.getElementById("history-list"); |
| 195 | + if (!totalEl || !countsEl || !listEl) return; |
| 196 | + |
| 197 | + const order = ["smartphones", "socs", "gpus", "cpus", "brands"]; |
| 198 | + const label = { smartphones: "Phones", socs: "SoCs", gpus: "GPUs", cpus: "CPUs", brands: "Brands" }; |
| 199 | + |
| 200 | + getJSON("v1/index.json").then((manifest) => { |
| 201 | + const rows = order |
| 202 | + .map((key) => ({ key, count: manifest.collections?.[key]?.count })) |
| 203 | + .filter((row) => row.count != null); |
| 204 | + const total = rows.reduce((sum, row) => sum + row.count, 0); |
| 205 | + totalEl.textContent = total.toLocaleString() + " records"; |
| 206 | + countsEl.innerHTML = rows.map((row) => |
| 207 | + `<div class="history-count"><span>${label[row.key]}</span><b>${row.count.toLocaleString()}</b></div>` |
| 208 | + ).join(""); |
| 209 | + }).catch(() => { |
| 210 | + totalEl.textContent = "sync unavailable"; |
| 211 | + countsEl.innerHTML = '<div class="history-count"><span>Static dump</span><b>offline</b></div>'; |
| 212 | + }); |
| 213 | + |
| 214 | + fetch("https://api.github.com/repos/GetTechAPI/TechAPI/commits?path=site/public/v1&per_page=5") |
| 215 | + .then((response) => { |
| 216 | + if (!response.ok) throw new Error(response.statusText); |
| 217 | + return response.json(); |
| 218 | + }) |
| 219 | + .then((commits) => { |
| 220 | + const items = Array.isArray(commits) ? commits.slice(0, 4) : []; |
| 221 | + if (!items.length) throw new Error("empty history"); |
| 222 | + listEl.innerHTML = items.map((item) => { |
| 223 | + const message = (item.commit?.message || "Dataset sync").split("\n")[0]; |
| 224 | + const date = item.commit?.committer?.date ? new Date(item.commit.committer.date) : null; |
| 225 | + const when = date ? date.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" }) : "recent"; |
| 226 | + const sha = String(item.sha || "").slice(0, 7); |
| 227 | + const url = item.html_url || "https://github.com/GetTechAPI/TechAPI"; |
| 228 | + return `<li><span class="history-dot"></span><span><a href="${esc(url)}" target="_blank" rel="noopener">${esc(message)}</a><small>${esc(when)} · ${esc(sha)}</small></span></li>`; |
| 229 | + }).join(""); |
| 230 | + }) |
| 231 | + .catch(() => { |
| 232 | + listEl.innerHTML = '<li><span class="history-dot"></span><span>Current static dump is available; repository history could not be loaded.<small>GitHub API unavailable</small></span></li>'; |
| 233 | + }); |
| 234 | +})(); |
| 235 | + |
183 | 236 | /* ============================================================ |
184 | 237 | PLAYGROUND |
185 | 238 | ============================================================ */ |
|
0 commit comments