From 60d7b0f24f3a1a3f18587e525e61183f013cdf30 Mon Sep 17 00:00:00 2001 From: kaireewu Date: Sat, 31 Oct 2026 23:50:19 +0800 Subject: [PATCH] fix(web): center active filters and compact mobile cards --- reaction_board/web/static/components.js | 9 +++++++++ reaction_board/web/static/style.css | 7 ++++++- reaction_board/web/static/views/fans.js | 3 ++- reaction_board/web/static/views/rankings.js | 3 ++- reaction_board/web/static/views/video.js | 4 +++- tools/seed_preview_db.py | 4 +++- 6 files changed, 25 insertions(+), 5 deletions(-) diff --git a/reaction_board/web/static/components.js b/reaction_board/web/static/components.js index 2b28ee0..0b33ea8 100644 --- a/reaction_board/web/static/components.js +++ b/reaction_board/web/static/components.js @@ -127,6 +127,15 @@ export function metricChips(metrics, active, dataName = "metric") { )).join(""); } +export function centerActiveChips(root) { + root.querySelectorAll(".chip-row.scroll-x").forEach((row) => { + const active = row.querySelector(".chip.active"); + if (!active) return; + const target = active.offsetLeft - row.clientWidth / 2 + active.clientWidth / 2; + row.scrollLeft = Math.max(0, target); + }); +} + export function pager(page, pages) { if (!pages || pages <= 1) return ""; return `
diff --git a/reaction_board/web/static/style.css b/reaction_board/web/static/style.css index 829fe1a..2ffa6a9 100644 --- a/reaction_board/web/static/style.css +++ b/reaction_board/web/static/style.css @@ -258,9 +258,10 @@ h1, h2, h3, p { margin: 0; } .notice-row { display: flex; + flex-wrap: wrap; align-items: center; justify-content: space-between; - gap: 12px; + gap: 4px 12px; padding: 11px 14px; border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 13px; @@ -501,6 +502,10 @@ h1, h2, h3, p { margin: 0; } .select { max-width: 170px; } .filter-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .filter-row > .chip-row { flex: 1; min-width: 200px; } +@media (max-width: 619px) { + .filter-row > .chip-row { flex-basis: 100%; min-width: 0; } + .filter-row .select { width: 100%; max-width: none; } +} .growth-days { padding-top: 2px; } .rank-list { display: flex; flex-direction: column; gap: 8px; } diff --git a/reaction_board/web/static/views/fans.js b/reaction_board/web/static/views/fans.js index 08527f5..26d6217 100644 --- a/reaction_board/web/static/views/fans.js +++ b/reaction_board/web/static/views/fans.js @@ -1,5 +1,5 @@ import { api } from "../api.js"; -import { emptyState, sectionHeader } from "../components.js"; +import { centerActiveChips, emptyState, sectionHeader } from "../components.js"; import { lineChart } from "../charts.js"; import { escapeAttr, escapeHtml, formatCount, formatDate, formatDelta, @@ -75,6 +75,7 @@ export async function renderFans(root, { query }) { root.querySelector("#fan-mid")?.addEventListener("change", (event) => { replaceQuery({ mid: event.target.value }); }); + centerActiveChips(root); const chartElement = root.querySelector("#fans-chart"); if (chartElement) { lineChart(chartElement, { diff --git a/reaction_board/web/static/views/rankings.js b/reaction_board/web/static/views/rankings.js index 5d74b4b..d33f04f 100644 --- a/reaction_board/web/static/views/rankings.js +++ b/reaction_board/web/static/views/rankings.js @@ -1,6 +1,6 @@ import { api, apiUrl } from "../api.js"; import { - chip, emptyState, pager, sectionHeader, upCard, videoCard, + centerActiveChips, chip, emptyState, pager, sectionHeader, upCard, videoCard, } from "../components.js"; import { escapeAttr, escapeHtml, formatCount } from "../format.js"; import { replaceQuery } from "../router.js"; @@ -161,4 +161,5 @@ export async function renderRankings(root, { query }) { if (button.disabled) return; replaceQuery({ page: button.dataset.page }); })); + centerActiveChips(root); } diff --git a/reaction_board/web/static/views/video.js b/reaction_board/web/static/views/video.js index a4e64c1..1c06457 100644 --- a/reaction_board/web/static/views/video.js +++ b/reaction_board/web/static/views/video.js @@ -1,6 +1,7 @@ import { api } from "../api.js"; import { - badge, cover, emptyState, metricChips, metricGrid, sectionHeader, videoCard, + badge, centerActiveChips, cover, emptyState, metricChips, metricGrid, + sectionHeader, videoCard, } from "../components.js"; import { lineChart, emptyChart } from "../charts.js"; import { @@ -110,6 +111,7 @@ export async function renderVideo(root, { params, query }) { root.querySelectorAll("[data-range]").forEach((button) => button.addEventListener("click", () => { replaceQuery({ range: button.dataset.range }); })); + centerActiveChips(root); const chartElement = root.querySelector("#video-trend"); if (chartElement) { diff --git a/tools/seed_preview_db.py b/tools/seed_preview_db.py index 96d6a53..118452a 100644 --- a/tools/seed_preview_db.py +++ b/tools/seed_preview_db.py @@ -484,6 +484,7 @@ def seed_preview( rows: list[tuple[str, int, dict[str, int], float]] = [] inserted_first_seen = 0 + expected_rows = 0 for video in selected: aid = int(video["aid"]) first_seen_ts = first_seen_map[aid] @@ -500,6 +501,7 @@ def seed_preview( synthetic_days = max(0, (latest_day - snapshot_start).days) if synthetic_days <= 0: continue + expected_rows += synthetic_days age_days = max( 0.0, (latest_day - shanghai_datetime(int(video["pubdate"] or 0)).date()).days, @@ -661,7 +663,7 @@ def seed_preview( "first_seen_updated": inserted_first_seen, "snapshots_inserted": len(rows) - failed, "snapshots_failed": failed, - "snapshots_missing": (len(selected) * days) - len(rows), + "snapshots_missing": expected_rows - len(rows), "up_stat_rows": up_rows, "preview_marker": "preview:seeded=1", }