diff --git a/CHANGELOG.md b/CHANGELOG.md index 8270144..5599b14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The Directory browses as Apple's does: a row of categories, and once one is picked, a row of its subcategories (Tech News under News, Video Games under Leisure), instead of one row mixing both. - A pinned item in a list wears the same disc as a pinned feed, in the theme's accent, instead of a plain solid pin. +- The Directory can be sorted by name, A to Z or Z to A, or by most subscribers. It still opens A to Z. - An item published without a title shows its opening words, in plain text rather than bold, instead of "(untitled)"; one with no text either shows its file's name, or its show and date. Opened, it starts with its text. - A feed that has moved for good (a permanent redirect) is followed to its new address, which iPX then reads from, and says so in the log as `feed_moved`. A temporary redirect changes nothing. - The daemon sleeps until the next feed is due, at most ten minutes, instead of looking every minute; refreshing or adding a feed still wakes it at once. diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index b93d95d..e2ca6b8 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -887,6 +887,16 @@ test('Popular lists what everyone here reads, but never a private feed', async ( await expect(tiles).toHaveCount(dir.filter(p => p.podcast).length); await pick('tabs', 'All').click(); await expect(tiles).toHaveCount(dir.length); + // Sorted by name either way, A to Z as it opens, or by subscribers (#122). + const byName = dir.map(p => p.title || p.id).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base', numeric: true })); + await piper.locator('#dirsort').selectOption('az'); + await expect(tiles.first()).toContainText(byName[0]); + await piper.locator('#dirsort').selectOption('za'); + await expect(tiles.first()).toContainText(byName[byName.length - 1]); + await expect(tiles).toHaveCount(dir.length); + await piper.locator('#dirsort').selectOption('subscribers'); + await expect(tiles).toHaveCount(dir.length); + await piper.locator('#dirsort').selectOption('az'); // Add a feed is for an address; Popular and Directory are where you browse (issue #30). await piper.locator('#addFeed').click(); diff --git a/web/app.css b/web/app.css index 5d0f404..9f65d7e 100644 --- a/web/app.css +++ b/web/app.css @@ -642,6 +642,7 @@ body.scan-this .fhead [data-a=scan] .i,body.scan-any .fhead [data-a=scanall] .i, are; a .badge's fill already means unread in the sidebar. */ .dirbar{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin-top:4px} .dirbar .tabs{flex:none} +#dirsort{flex:none;width:auto;padding:4px 6px;font-size:13px} .chips{display:flex;flex-wrap:wrap;gap:2px 6px;flex:1 1 0;min-width:0} /* A category's subcategories go on a line of their own, under the category picked. */ .chips.subs{flex-basis:100%} diff --git a/web/src/dialogs.ts b/web/src/dialogs.ts index 461484f..09bb5cd 100644 --- a/web/src/dialogs.ts +++ b/web/src/dialogs.ts @@ -70,7 +70,14 @@ function listedFeed(p,cls){ // Directory's filters. Kept out here because a finished scan redraws the pane, which would // otherwise clear them. -let dirKind='All', dirCat=null, dirSub=null; +let dirKind='All', dirCat=null, dirSub=null, dirSort='az'; +/// The Directory's orders. It opens A to Z, as the server sends it. +const NAME=p=>(p.title||p.id); +const DIR_SORTS={ + subscribers:['Most subscribers',(a,b)=>b.subscribers-a.subscribers||NAME(a).localeCompare(NAME(b),undefined,{sensitivity:'base',numeric:true})], + az:['Name, A to Z',(a,b)=>NAME(a).localeCompare(NAME(b),undefined,{sensitivity:'base',numeric:true})], + za:['Name, Z to A',(a,b)=>NAME(b).localeCompare(NAME(a),undefined,{sensitivity:'base',numeric:true})], +}; const KINDS={All:()=>true,Podcasts:p=>p.podcast,Blogs:p=>!p.podcast}; /// Directory: every listed feed as its cover art, under two filters that combine: what a feed is /// (Podcasts, anything with audio or video, or Blogs, the rest) and what it is about (its iTunes @@ -96,6 +103,8 @@ async function renderDirectory(url,box){ bar.innerHTML= (both?`