diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c4dd42..6561c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ The long form, with what was wrong before and how it was found, is in items show without pressing Scan. - A file deleted to save space, or by hand, looks as if it was never downloaded: no "reaped" label, just the Download button. The retention summary says "deleted", not "reaped". +- The Files pane and an item's own buttons are icons, with the words in their tooltips: save, + delete, view, download, mark read, keep, and open the original. ### Security diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index fe327dd..f23e1d7 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -78,7 +78,7 @@ test('the three panes are there and the item text lands in the bottom one', asyn const downloaded = page.locator('.ep', { hasText: 'downloaded' }).first(); await downloaded.click(); await expect(page.locator('#files audio')).toBeVisible(); - await expect(page.locator('#files .encbox .btn', { hasText: 'Save' })).toBeVisible(); + await expect(page.locator('#files .encbox [title="Save to this computer"]')).toBeVisible(); // Selecting another item replaces the pane rather than stacking. await page.locator('.ep', { hasText: 'First Episode' }).click(); @@ -101,8 +101,8 @@ test('a downloaded file that is not audio gets no player', async ({ page }) => { await expect(page.locator('#files .encbox')).toContainText('image'); await expect(page.locator('#files .encbox')).toContainText('downloaded'); // Still offered as a file, just not as an episode: viewable and keepable. - await expect(page.locator('#files .btn', { hasText: 'Save' })).toBeVisible(); - const view = page.locator('#files a', { hasText: 'View' }); + await expect(page.locator('#files [title="Save to this computer"]')).toBeVisible(); + const view = page.locator('#files a[title="View in a new tab"]'); await expect(view).toHaveAttribute('target', '_blank'); await expect(view).toHaveAttribute('rel', /noopener/); await expect(view).toHaveAttribute('href', /\/media\/\d+/); @@ -213,7 +213,7 @@ test('inside an OPML, feeds with unread items are listed first', async ({ page } await side.getByText('Grouped Show').click(); const gs = page.locator('.ep', { hasText: 'Grouped Ep' }); await gs.click(); - await page.locator('#detail button', { hasText: 'Mark unread' }).click(); + await page.locator('#detail button[title="Mark unread"]').click(); await expect(gs).not.toHaveClass(/read/); // Aardvark comes first alphabetically and in the OPML, so only the unread sort puts @@ -281,9 +281,9 @@ test('opening an item marks it read, and the toggle flips it back', async ({ pag // Another test may have opened this item already, so start from a known state: the // toggle in the text below flips it back -- which used to recurse until the stack blew. await row().click(); - await page.locator('#detail button', { hasText: 'Mark unread' }).click(); + await page.locator('#detail button[title="Mark unread"]').click(); await expect(row()).not.toHaveClass(/read/); - await expect(page.locator('#detail button', { hasText: 'Mark read' })).toBeVisible(); + await expect(page.locator('#detail button[title="Mark read"]')).toBeVisible(); // Opening it is reading it. await row().click(); @@ -375,8 +375,9 @@ test('deleting a shared file warns that it is everyone\'s copy', async ({ page } await expect(row).toBeVisible({ timeout: 20_000 }); await row.click(); - const del = page.locator('#files button', { hasText: 'Delete' }); - await expect(del).toHaveText('Delete for everyone'); + // An icon now; what it does, and to whom, is in its tooltip. + const del = page.locator('#files button[data-a="del"]'); + await expect(del).toHaveAttribute('title', /^Delete for everyone \(shared with 1 other person/); // Two prompts: the page's own, then the server's, because someone else has not played // it. Accept the first, decline the second, and the file must survive. @@ -657,12 +658,12 @@ test('a deleted file looks as if it was never downloaded', async ({ page }) => { const row = page.locator('.ep', { hasText: 'An Article' }); await expect(row).toBeVisible({ timeout: 20_000 }); await row.click(); - await page.locator('#files button', { hasText: 'Delete' }).click(); + 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).not.toContainText('downloaded'); await expect(row).not.toContainText(/reaped/i); await row.click(); await expect(page.locator('#files')).not.toContainText(/reaped/i); - await expect(page.locator('#files .btn', { hasText: 'Download' })).toBeVisible(); + await expect(page.locator('#files button[title="Download to the server"]')).toBeVisible(); }); diff --git a/web/index.html b/web/index.html index aa707fb..5b94539 100644 --- a/web/index.html +++ b/web/index.html @@ -214,6 +214,8 @@ a.btn{text-decoration:none;color:inherit} .btn.primary{background:var(--accent);border-color:var(--accent);color:var(--ink);font-weight:600} .btn.primary:hover{filter:brightness(1.08)} .btn.danger:hover{border-color:var(--bad);color:var(--bad)} +/* An icon in place of a word; the word is in its tooltip. */ +.btn.ico{padding:4px 9px;min-width:32px;font-size:14px;line-height:1.25;text-align:center} .toolbar{ display:flex;gap:10px;align-items:center;margin-bottom:12px;flex-wrap:wrap; position:sticky;top:0;background:var(--bg);padding:6px 0 8px;z-index:3; @@ -983,9 +985,12 @@ function showDetail(e){ ${num?`${num}`:''} ${dateOf(e.published)} ${e.duration?`${clock(e.duration)}`:''} - - - ${e.link?`Open original \u2197`:''} + + + ${e.link?`\u2197`:''}