From 00f6293b95eea29c7c60308a231dd6fc92e1b9d3 Mon Sep 17 00:00:00 2001 From: rays Date: Mon, 28 Sep 2026 23:50:41 +0000 Subject: [PATCH] The refresh button turns while its feed is checked (#78) The feed events already put a spinner on the sidebar row, which a phone hides. The same state now sets scan-this (the open feed, or a feed in the open folder) and scan-any (any of your feeds) on , and the refresh icons turn under them. On because the feed page is redrawn as items come in. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ tests/ui/app.spec.js | 17 +++++++++++++++++ web/app.css | 2 ++ web/src/feedpage.ts | 1 + web/src/feeds.ts | 5 +++++ 5 files changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ac5939..a8d22cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - A feed that has no artwork of its own shows its website's icon instead. +- The refresh button turns while its feed is being checked, and the check-every-feed buttons + while any of yours is. - Adding a website's address subscribes to the feed that site links, instead of failing on every scan. - `/api/status` gives the number of feeds, items waiting to download and files downloaded, and the version, for a dashboard such as Homepage. diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index 13218fd..a68569c 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -1443,3 +1443,20 @@ test('Share hands the feed, the item and the file to the share sheet', async ({ { title: 'Second Episode', url: expect.stringContaining('/ep1.mp3?2') }, ]); }); + +test('a refresh button turns while what it checks is being checked', async ({ page }) => { + await page.locator('.feed', { hasText: 'Test Show' }).click(); + const spin = sel => page.locator(sel).evaluate(el => getComputedStyle(el).animationName); + const btn = '.fhead [data-a=scan] .i'; + await expect(page.locator(btn)).toBeVisible(); + expect(await spin(btn)).toBe('none'); + await page.evaluate(() => setScanning('test-show', true)); + expect(await spin(btn)).toBe('ipxspin'); + expect(await spin('#scanAll .i')).toBe('ipxspin'); + // Another feed being checked turns the toolbar's, not this page's. + await page.evaluate(() => { setScanning('test-show', false); setScanning('multi-show', true); }); + expect(await spin(btn)).toBe('none'); + expect(await spin('#scanAll .i')).toBe('ipxspin'); + await page.evaluate(() => setScanning('multi-show', false)); + expect(await spin('#scanAll .i')).toBe('none'); +}); diff --git a/web/app.css b/web/app.css index 232a6e5..a3623d5 100644 --- a/web/app.css +++ b/web/app.css @@ -586,6 +586,8 @@ input:focus,select:focus{outline:0;border-color:var(--accent)} border:2px solid var(--line);border-top-color:var(--accent);animation:ipxspin .8s linear infinite; } @keyframes ipxspin{to{transform:rotate(360deg)}} +body.scan-this .fhead [data-a=scan] .i,body.scan-any .fhead [data-a=scanall] .i,body.scan-any #scanAll .i{ + animation:ipxspin 1s linear infinite} /* A feed's error mark, in the triangle's place: the same column as every folder's triangle, a child's included, which is why it moves left by the child's indent. */ .ferr{position:absolute;left:-16px;top:0;bottom:0;width:24px;display:grid;place-items:center;color:var(--bad)} diff --git a/web/src/feedpage.ts b/web/src/feedpage.ts index ee33404..14ef733 100644 --- a/web/src/feedpage.ts +++ b/web/src/feedpage.ts @@ -1,5 +1,6 @@ /* ---------------- feed page ---------------- */ function renderFeed(){ + paintScanning(); // another feed's page may be the one open now const box=$('#content'); box.classList.remove('plain'); const v=VIEWS[S.feed]; diff --git a/web/src/feeds.ts b/web/src/feeds.ts index 9bcb6b7..517c145 100644 --- a/web/src/feeds.ts +++ b/web/src/feeds.ts @@ -39,6 +39,11 @@ function paintScanning(){ row.classList.toggle('scanning',on); if(on) row.title='Checking for new items…'; else row.removeAttribute('title'); } + // The refresh buttons turn while what they check is being checked (#78). On , because + // a feed's page is redrawn as its items come in and would take a class on the button with it. + const busy=id=>scanning.has(id)||S.feeds.some(c=>c.group===id&&scanning.has(c.id)); + document.body.classList.toggle('scan-this',busy(S.feed)); + document.body.classList.toggle('scan-any',S.feeds.some(f=>scanning.has(f.id))); } function renderFeeds(){