From 2d350a7601e91605b90ded18ed5f360b5215ea40 Mon Sep 17 00:00:00 2001 From: Young Lee <8462583+yl8976@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:49:36 -0800 Subject: [PATCH] feat(admin): style search + clarify bulk actions --- AGENTS.md | 1 + README.md | 4 +- src/routes/admin.ts | 281 +++++++++++++++++++++++---------------- src/styles/components.ts | 33 +++++ src/styles/layout.ts | 8 +- 5 files changed, 205 insertions(+), 122 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fb3ce15..ef10613 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,6 +80,7 @@ Notes: - First choice: use dashboard bulk actions (`/admin`) with search + checkbox selection. - Use **Table** view for bulk delete. - Table columns are resizable and sortable; widths persist per-browser via localStorage. +- **Select Results** selects all rows currently shown by the search filter; **Clear Selection** unselects everything. - Avoid wildcard deletion; prefer search + small batches to reduce risk of deleting legitimate feeds. ## Cloudflare/Wrangler conventions diff --git a/README.md b/README.md index 526df80..e79f5dd 100644 --- a/README.md +++ b/README.md @@ -114,8 +114,8 @@ npm run build 3. Use the search box to filter obvious spam feeds. - Long titles/URLs are truncated; hover to see the full value. Click to copy. - Drag the column separators to resize; click headers to sort (double-click a separator to reset width). -4. Select rows and use **Delete Selected Feeds**. -5. For legitimate feeds that got spam emails, open **Emails**, filter by subject, then **Delete Selected Emails**. +4. Use **Select Results** to select the filtered rows, then click **Delete Selected**. +5. For legitimate feeds that got spam emails, open **Emails**, filter by subject, then **Select Results** and **Delete Selected**. ## Upgrading dependencies diff --git a/src/routes/admin.ts b/src/routes/admin.ts index a64713a..b535e96 100644 --- a/src/routes/admin.ts +++ b/src/routes/admin.ts @@ -352,35 +352,6 @@ app.get("/", async (c) => { : view === "table" ? html`
-
-
- - - - 0 selected -
-
-
{ > +
+
+ + Showing ${feedsWithConfig.length} + 0 selected + + + +
+
+
@@ -617,17 +628,6 @@ app.get("/", async (c) => {
- -
- -
` @@ -810,6 +810,7 @@ app.get("/", async (c) => { let FEED_ROWS = []; let FEED_CHECKBOXES = []; let FEED_SELECTED_COUNT_EL = null; + let FEED_MATCH_COUNT_EL = null; let FEED_BULK_DELETE_BUTTON_EL = null; let FEED_SELECT_ALL_EL = null; let FEED_FILTER_TIMER = null; @@ -821,13 +822,23 @@ app.get("/", async (c) => { FEED_ROWS = Array.from(document.querySelectorAll('.feed-row')); FEED_CHECKBOXES = Array.from(document.querySelectorAll('.feed-select')); FEED_SELECTED_COUNT_EL = document.getElementById('selected-feed-count'); + FEED_MATCH_COUNT_EL = document.getElementById('feed-match-count'); FEED_BULK_DELETE_BUTTON_EL = document.getElementById('bulk-delete-feeds-button'); FEED_SELECT_ALL_EL = document.getElementById('select-all-feeds'); setupFeedTableResizing(); setupFeedTableSorting(); + updateFeedMatchCount(); updateFeedSelectionState(); } + function updateFeedMatchCount() { + if (!FEED_MATCH_COUNT_EL) return; + const total = FEED_ROWS.length; + const visible = FEED_ROWS.filter((row) => !row.hidden).length; + const query = (document.getElementById('feed-search')?.value || '').trim(); + FEED_MATCH_COUNT_EL.textContent = query ? ('Showing ' + visible + ' of ' + total) : ('Showing ' + total); + } + function scheduleFeedFilter() { if (FEED_FILTER_TIMER) { clearTimeout(FEED_FILTER_TIMER); @@ -1052,12 +1063,24 @@ app.get("/", async (c) => { updateFeedSelectionState(); } + function selectMatchingFeeds() { + setVisibleFeedSelection(true); + } + + function clearFeedSelection() { + FEED_CHECKBOXES.forEach((checkbox) => { + checkbox.checked = false; + }) + updateFeedSelectionState(); + } + function filterFeedRows() { const query = (document.getElementById('feed-search')?.value || '').toLowerCase().trim(); FEED_ROWS.forEach((row) => { const haystack = row.getAttribute('data-search') || ''; row.hidden = !!query && !haystack.includes(query); }); + updateFeedMatchCount(); updateFeedSelectionState(); } @@ -1520,38 +1543,51 @@ app.get("/feeds/:feedId/emails", async (c) => { : ""} ${feedMetadata.emails.length > 0 ? html` -
-
- - - - 0 selected -
-
-
+
+
+ + Showing ${feedMetadata.emails.length} + 0 selected + + + +
+
+
@@ -1641,16 +1677,6 @@ app.get("/feeds/:feedId/emails", async (c) => {
-
- -
` : html`
@@ -1663,32 +1689,43 @@ app.get("/feeds/:feedId/emails", async (c) => {