From 57dcba2d1ae6f6706250f3f84e392f9e732334ad Mon Sep 17 00:00:00 2001 From: rays Date: Fri, 11 Sep 2026 15:20:32 +0000 Subject: [PATCH] One file icon, green when downloaded; mark unread is an envelope - The separate check (and warning) beside a file's type icon is gone: the type icon itself is green once the file is downloaded and red when the download failed, with the details in its tooltip. One icon per row keeps the column lined up. On Classic's blue selection they are a lighter green and red rather than white. - Mark unread under an item's title was a solid circle, which read as a record button. It is Font Awesome's closed envelope now. - Drops the unused circle-check, circle-exclamation and circle icons. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_016HdTEWQNrzyFULijigkmMn --- CHANGELOG.md | 8 ++++---- tests/ui/app.spec.js | 11 +++++------ web/index.html | 44 ++++++++++++++++++++------------------------ 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bdd584..1ea5264 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,10 +37,10 @@ The long form, with what was wrong before and how it was found, is in Download latest dialogs (save, download, cancel). The icons are Font Awesome Free, embedded as SVG: only the ones used, no font to download, and nothing fetched from anyone else. They replace font characters such as ⟳ ⤓ ↗, which came out thin and tiny and differed from font to - font. Keep is a flag everywhere, as it was in the original. -- A file's state and type are icons: a check when it is downloaded, a warning when it failed - (the error is in the tooltip), and nothing while it waits; audio, video, image, PDF, torrent - and other files each have their own. The DOWNLOADED and PENDING labels are gone. + font. Keep is a flag everywhere, as it was in the original, and mark unread is an envelope. +- A file's type is an icon (audio, video, image, PDF, torrent, other), green once it is + downloaded and red when the download failed, with the details in its tooltip. One icon per + row keeps the column lined up. The DOWNLOADED and PENDING labels are gone. ### Security diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index 03ca1b9..bfd5ef7 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -87,7 +87,7 @@ test('the three panes are there and the item text lands in the bottom one', asyn // Only the downloaded one gets a player, and max_new_per_check is 1, so find it by // its chip rather than assuming which episode the daemon happened to fetch. - const downloaded = page.locator('.ep', { has: page.locator('[title="Downloaded"]') }).first(); + const downloaded = page.locator('.ep', { has: page.locator('.kind.here') }).first(); await downloaded.click(); await expect(page.locator('#files audio')).toBeVisible(); await expect(page.locator('#files .encbox [title="Save to this computer"]')).toBeVisible(); @@ -110,9 +110,8 @@ test('a downloaded file that is not audio gets no player', async ({ page }) => { await expect(page.locator('#detail .dt')).toHaveText('An Article'); await expect(page.locator('#files audio')).toHaveCount(0); - // What it is and that it is here, as icons with the words in their tooltips. - await expect(page.locator('#files .encbox [title="image"]')).toBeVisible(); - await expect(page.locator('#files .encbox [title="Downloaded"]')).toBeVisible(); + // What it is and that it is here: one icon, green, with the words in its tooltip. + await expect(page.locator('#files .encbox .kind.here')).toHaveAttribute('title', 'image, downloaded'); // Still offered as a file, just not as an episode: viewable and keepable. await expect(page.locator('#files [title="Save to this computer"]')).toBeVisible(); const view = page.locator('#files a[title="View in a new tab"]'); @@ -131,7 +130,7 @@ test('an item with several enclosures lists them all', async ({ page }) => { await row.click(); // The Files pane lists every one: the audio and the image. await expect(page.locator('#files .encbox')).toHaveCount(2); - await expect(page.locator('#files .encbox').nth(1).locator('[title="image"]')).toBeVisible(); + await expect(page.locator('#files .encbox').nth(1).locator('.kind[title^="image"]')).toBeVisible(); }); test('the filter tabs change what is listed', async ({ page }) => { @@ -674,7 +673,7 @@ test('a deleted file looks as if it was never downloaded', async ({ page }) => { await page.locator('#files button[data-a="del"]').click(); // No "reaped", no chip at all: just the way to get it again. - await expect(row.locator('[title="Downloaded"]')).toHaveCount(0); + await expect(row.locator('.kind.here')).toHaveCount(0); await expect(row).not.toContainText(/reaped/i); await row.click(); await expect(page.locator('#files')).not.toContainText(/reaped/i); diff --git a/web/index.html b/web/index.html index 467e734..bb9acea 100644 --- a/web/index.html +++ b/web/index.html @@ -238,11 +238,11 @@ a.btn{text-decoration:none;color:inherit} .btn.ico{padding:4px 9px;min-width:32px;font-size:14px;line-height:1.25;text-align:center} /* An icon (Font Awesome, embedded as SVG) in the button's own colour. */ .i{display:inline-block;width:16px;height:16px;vertical-align:-3px;flex:none;fill:currentColor} -/* A file's state and type, as icons in place of the old DOWNLOADED / PENDING / audio chips. */ -.fst,.kind{display:inline-grid;place-items:center} -.fst{color:var(--good)} -.fst.bad{color:var(--bad)} -.kind{color:var(--dim)} +/* A file's type as an icon, in place of the old DOWNLOADED / PENDING / audio chips: green once + it is here, red when the download failed. */ +.kind{display:inline-grid;place-items:center;color:var(--dim)} +.kind.here{color:var(--good)} +.kind.bad{color:var(--bad)} .fhead .art .i{width:22px;height:22px} .toolbar{ display:flex;gap:10px;align-items:center;margin-bottom:12px;flex-wrap:wrap; @@ -503,8 +503,10 @@ input[type=range]::-moz-range-thumb{width:12px;height:12px;border:0;border-radiu :root[data-theme="classic"] .ep.sel .st, :root[data-theme="classic"] .ep.sel .fl, :root[data-theme="classic"] .ep.sel .file, -:root[data-theme="classic"] .ep.sel .fst, :root[data-theme="classic"] .ep.sel .kind{color:#fff} +/* Green and red stay green and red on the blue, just lighter so they read. */ +:root[data-theme="classic"] .ep.sel .kind.here{color:#a6f3a6} +:root[data-theme="classic"] .ep.sel .kind.bad{color:#ffb8ad} /* Lists were white in the original; the pale blue-grey belongs to the source list alone. */ :root[data-theme="classic"] .childrow{background:#fff} :root[data-theme="classic"] .dt{background:linear-gradient(#80aae6,#3f78cf);color:#fff;padding:6px 12px;border-radius:4px} @@ -616,9 +618,7 @@ const ICON={ directory:fa('0 0 448 512',''), // solid/table-list popular:fa('0 0 576 512',''), // solid/star all:fa('0 0 512 512',''), // solid/layer-group - dot:fa('0 0 512 512',''), // solid/circle - done:fa('0 0 512 512',''), // solid/circle-check - alert:fa('0 0 512 512',''), // solid/circle-exclamation + unread:fa('0 0 512 512',''), // solid/envelope: a closed letter, not a record button audio:fa('0 0 448 512',''), // solid/headphones video:fa('0 0 576 512',''), // solid/video image:fa('0 0 448 512',''), // solid/image @@ -973,7 +973,7 @@ function epEl(e){ ${esc(feedName(e.feed_id))}
- ${enc?stateIcon(enc)+kindIcon(enc):''} + ${enc?kindIcon(enc):''} ${enc&&enc.length?`${mb(enc.length)}`:''} ${enc&&!has?`
`:''}
@@ -1013,20 +1013,16 @@ function kindOf(enc){ return (ext && ext.length<=5) ? ext.toLowerCase() : 'file'; } -/// What a file is, as an icon; the word is in its tooltip. +/// What a file is, as one icon coloured by whether it is here: green once downloaded, red when +/// the download failed, plain otherwise, so a file waiting and one deleted read alike. One icon +/// either way keeps the column lined up; the words are in its tooltip. function kindIcon(enc){ const k=kindOf(enc); const i={audio:ICON.audio,video:ICON.video,image:ICON.image,pdf:ICON.doc,torrent:ICON.torrent}[k]||ICON.file; - return `${i}`; -} - -/// Whether a file is here, as an icon: a check when downloaded, a warning when it failed, and -/// nothing otherwise, so a file waiting to download and one deleted to save space read alike. -function stateIcon(enc){ - if(enc.path) return `${ICON.done}`; - if(enc.state!=='error') return ''; - const why=esc(enc.last_error||'Download failed'); - return `${ICON.alert}`; + const [cls,label]=enc.path ? [' here',`${k}, downloaded`] + : enc.state==='error' ? [' bad',`${k}, download failed${enc.last_error?': '+enc.last_error:''}`] + : ['',k]; + return `${i}`; } function feedArt(id=S.feed){ const f=S.feeds.find(x=>x.id===id); return f&&f.image; } @@ -1118,7 +1114,7 @@ function showDetail(e){ ${dateOf(e.published)} ${e.duration?`${clock(e.duration)}`:''} + aria-label="Mark ${e.read?'unread':'read'}">${e.read?ICON.unread:ICON.check} ${e.link?` - ${kindIcon(x)}${stateIcon(x)} + ${kindIcon(x)} ${size} ${ICON.open} ${saveBtn} @@ -1172,7 +1168,7 @@ function encBox(x){ // through here, which would make ipx a fetch-anything relay. const viewable = !isPlayable(x) && x.state !== 'pending'; return `
- ${kindIcon(x)}${stateIcon(x)} + ${kindIcon(x)} ${size} ${x.state==='error'&&x.last_error?`${esc(x.last_error)}`:''} ${viewable?`${ICON.open}`:''}