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 <body>, and the refresh
icons turn under them. On <body> because the feed page is redrawn as items
come in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 23:50:41 +00:00
parent bae22e553e
commit 00f6293b95
5 changed files with 27 additions and 0 deletions

View File

@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- A feed that has no artwork of its own shows its website's icon instead. - 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. - 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 - `/api/status` gives the number of feeds, items waiting to download and files downloaded, and
the version, for a dashboard such as Homepage. the version, for a dashboard such as Homepage.

View File

@@ -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') }, { 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');
});

View File

@@ -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; border:2px solid var(--line);border-top-color:var(--accent);animation:ipxspin .8s linear infinite;
} }
@keyframes ipxspin{to{transform:rotate(360deg)}} @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 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. */ 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)} .ferr{position:absolute;left:-16px;top:0;bottom:0;width:24px;display:grid;place-items:center;color:var(--bad)}

View File

@@ -1,5 +1,6 @@
/* ---------------- feed page ---------------- */ /* ---------------- feed page ---------------- */
function renderFeed(){ function renderFeed(){
paintScanning(); // another feed's page may be the one open now
const box=$('#content'); const box=$('#content');
box.classList.remove('plain'); box.classList.remove('plain');
const v=VIEWS[S.feed]; const v=VIEWS[S.feed];

View File

@@ -39,6 +39,11 @@ function paintScanning(){
row.classList.toggle('scanning',on); row.classList.toggle('scanning',on);
if(on) row.title='Checking for new items…'; else row.removeAttribute('title'); 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 <body>, 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(){ function renderFeeds(){