diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f2d476..6bee9b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Checking every feed no longer makes every icon in the feed list flash while it runs: the list keeps the icons it has already drawn. + ## [0.10.0] - 2026-10-05 ### Added diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index da319d1..8b711bb 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -966,6 +966,24 @@ test('the Directory lists what everyone here reads, the most subscribed first, b await ctx.close(); }); +test('the feed list keeps the icons it has drawn when a feed\'s new row comes in (#134)', async ({ page }) => { + await expect(page.locator('#feedlist .feed').first()).toBeVisible({ timeout: 20_000 }); + const kept = await page.evaluate(async () => { + const frames = () => new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))); + // Artwork that loads, on a feed of its own row: the fixtures' fails on purpose, for initials. + const row = { ...S.feeds.find(f => !f.group && !S.feeds.some(c => c.group === f.id)), image: '/favicon.png' }; + patchFeed(row); await frames(); + const rows = [...document.querySelectorAll('#feedlist .feed')]; + const before = [...document.querySelectorAll('#feedlist .feed img')]; + // The same feed's new row again, as a check of every feed sends one for each feed it reads. + patchFeed({ ...row }); await frames(); + const after = [...document.querySelectorAll('#feedlist .feed img')]; + return { drawn: before.length > 0, redrawn: !rows[0].isConnected, same: after.length === before.length && after.every((img, i) => img === before[i]) }; + }); + // The rows are new, the images in them the ones already drawn. + expect(kept).toEqual({ drawn: true, redrawn: true, same: true }); +}); + test('adding a feed scans it straight away', async ({ page }) => { await page.locator('#addFeed').click(); await page.locator('#nurl').fill('http://127.0.0.1:8792/fresh.xml'); diff --git a/web/src/feeds.ts b/web/src/feeds.ts index b2471a2..9145180 100644 --- a/web/src/feeds.ts +++ b/web/src/feeds.ts @@ -66,6 +66,11 @@ function renderFeeds(){ const row=back&&list.querySelector(`[data-id="${CSS.escape(back[0])}"]`); if(row) (back[1]&&$('.chev',row)||row).focus(); }; + // The images already drawn, to go into the rows that replace theirs. Made anew, every icon in + // the list went blank and loaded again, once a frame through a check of every feed, as each + // feed's new row came in (#134). Matched by their markup, so a changed one is made anew. + const drawn=new Map(); + for(const img of $$('img',list)){ const k=img.outerHTML; if(!drawn.has(k)) drawn.set(k,[]); drawn.get(k).push(img); } list.innerHTML=''; const unreadAll=S.feeds.reduce((n,f)=>n+(f.unread||0),0); const places=document.createElement('div'); @@ -139,6 +144,7 @@ function renderFeeds(){ `${unread>999?'999+':unread}`; el.onclick=()=>{ selectFeed(f.id); nav(false); }; if(kids) $('.chev',el).onclick=ev=>{ ev.stopPropagation(); toggleGroup(f.id); }; + for(const img of $$('img',el)){ const was=drawn.get(img.outerHTML)?.shift(); if(was) img.replaceWith(was); } list.appendChild(el); } paintScanning();