diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a8574e..01767eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ The long form, with what was wrong before and how it was found, is in - Directory shows each feed as its cover art in a grid, title and subscriber count underneath, instead of a list. Popular and the Add a feed dialog keep their rows. +- Currently Listening is its own place in the feed list, below Popular, instead of a section at + the bottom of the Popular page. - The first scan after upgrading fetches every feed in full once, on its usual schedule, so each picks up its category without waiting for the publisher to change something. diff --git a/tests/page-smoke.js b/tests/page-smoke.js index 2ed4fd4..69edab3 100644 --- a/tests/page-smoke.js +++ b/tests/page-smoke.js @@ -88,6 +88,7 @@ const drive = [ ['opmlModal', () => ctx.opmlModal()], ['selectFeed (directory)', () => ctx.selectFeed(':directory')], ['selectFeed (popular)', () => ctx.selectFeed(':popular')], + ['selectFeed (currently listening)', () => ctx.selectFeed(':listening')], ['selectFeed (all subscriptions)', () => ctx.selectFeed(':all')], ['logsModal', () => ctx.logsModal()], // `const S` is not reachable from here: top-level const/let do not become properties diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index e49e007..630a980 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -155,7 +155,7 @@ test('an item with several enclosures lists them all', async ({ page }) => { await expect(page.locator('#files .encbox').nth(1).locator('.kind[title^="image"]')).toBeVisible(); }); -test('Currently Listening, below Popular, resumes an episode you started', async ({ page }) => { +test('Currently Listening, its own place below Popular, resumes an episode you started', async ({ page }) => { // Second Episode (900s) is 42 seconds in and unfinished. Which of Test Show's two episodes // the daemon auto-downloaded is not fixed (see the three-panes test above), so an earlier // test may have opened -- and so read -- this one already; reset it before relying on it. @@ -164,7 +164,14 @@ test('Currently Listening, below Popular, resumes an episode you started', async await page.evaluate(() => api('/api/entries/test-show/ui-2/position', { method: 'POST', body: JSON.stringify({ secs: 42 }) })); + // Popular lists feeds and nothing else; the episodes have a place of their own under it. await page.locator('#feedlist .place', { hasText: 'Popular' }).click(); + await expect(page.locator('#popular')).toBeVisible(); + await expect(page.locator('#listening')).toHaveCount(0); + const places = await page.locator('#feedlist .place b').allTextContents(); + expect(places.indexOf('Currently Listening')).toBe(places.indexOf('Popular') + 1); + + await page.locator('#feedlist .place', { hasText: 'Currently Listening' }).click(); const row = page.locator('#listening .childrow', { hasText: 'Second Episode' }); await expect(row).toBeVisible({ timeout: 20_000 }); await expect(row).toContainText('0:42 of 15:00'); @@ -177,7 +184,7 @@ test('Currently Listening, below Popular, resumes an episode you started', async // Finished (read) drops it from the list, however far it got. await page.evaluate(() => api('/api/entries/test-show/ui-2/flags', { method: 'POST', body: JSON.stringify({ read: true }) })); - await page.locator('#feedlist .place', { hasText: 'Popular' }).click(); + await page.locator('#feedlist .place', { hasText: 'Currently Listening' }).click(); await expect(page.locator('#listening')).not.toContainText('Second Episode', { timeout: 20_000 }); }); diff --git a/web/index.html b/web/index.html index 4b7c05d..a284491 100644 --- a/web/index.html +++ b/web/index.html @@ -855,6 +855,8 @@ const VIEWS={ blurb:'Every feed anyone on this server subscribes to, A to Z. The feeds inside an OPML are listed one by one, not the OPML.'}, ':popular':{title:'Popular',icon:ICON.popular,url:'/api/popular', blurb:'The ten feeds with the most subscribers here. The feeds inside an OPML count one by one, not the OPML.'}, + ':listening':{title:'Currently Listening',icon:ICON.audio,url:'/api/entries?filter=in_progress&limit=50', + blurb:'Episodes you started and have not finished, across every feed you subscribe to. Pick one up where you left off.'}, ':all':{title:'All Subscriptions',icon:ICON.all}, }; function renderFeeds(){ @@ -1745,30 +1747,26 @@ async function renderListed(v){ $('#tbRemove').disabled=true; syncTools(null); $('#epSearch').placeholder='Search items…'; - const listening=v===VIEWS[':popular'], grid=v===VIEWS[':directory']; + const listening=v===VIEWS[':listening'], grid=v===VIEWS[':directory']; box.innerHTML=`
${v.icon}

${v.title}

-
${v.blurb} Everyone counts, you included. Private feeds are never listed.
+
${v.blurb}${listening?'':' Everyone counts, you included. Private feeds are never listed.'}
${grid?'
':''} - - ${listening?`
Currently Listening
-

Loading…

`:''}`; +

Loading…

`; $('#count').textContent=v.title; - const n=await (grid?renderDirectory:listFeeds)(v.url,$('#popular',box)); - if(VIEWS[S.feed]===v) $('#count').textContent=`${v.title}: ${n} feed${n===1?'':'s'}`; - if(listening) renderListening(); + const n=await (listening?renderListening:grid?renderDirectory:listFeeds)(v.url,$(listening?'#listening':'#popular',box)); + if(VIEWS[S.feed]===v) $('#count').textContent=`${v.title}: ${plural(n,listening?'episode':'feed')}`; } -/// Below Popular: episodes you started and have not finished, across every feed you +/// Currently Listening: episodes you started and have not finished, across every feed you /// subscribe to. A row resumes the episode in the player bar on click -- a shortcut back to /// where you left off, not another way to browse. -async function renderListening(){ - const box=$('#listening'); +async function renderListening(url,box){ let rows=[]; - try{ rows=(await api('/api/entries?filter=in_progress&limit=10')).entries||[]; }catch{} + try{ rows=(await api(url)).entries||[]; }catch{} box.innerHTML=rows.length?'':'

Nothing in progress. Episodes you start and do not finish show up here.

'; for(const e of rows){ const el=document.createElement('div'); @@ -1782,6 +1780,7 @@ async function renderListening(){ el.onclick=()=>play(e); box.appendChild(el); } + return rows.length; } // The toolbar acts on whatever is selected: the feed on the left, the item in the table.