Never list paid-feed services; scan on add; no "reaped"; slimmer header

- Security: feeds from Patreon, Supercast, Supporting Cast, Glow and
  Memberful are never listed in Popular or the Directory. A Supercast
  feed keeps its key in the URL's path, which the query check missed, so
  it was being listed.
- Adding a feed queues a scan of it, and an OPML import that added feeds
  scans what is due, so items show without pressing Scan.
- A file deleted to save space, or by hand, looks as if it was never
  downloaded: no "reaped" chip, just the Download button. The retention
  summary says "deleted".
- The feed header keeps its title and stats to one line each and wraps
  its buttons; a single feed's table drops the Feed column.
- Tests: adding a feed shows its item without Scan; a deleted file shows
  no "reaped"; paid-feed hosts and acast public ids in the unit test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016HdTEWQNrzyFULijigkmMn
This commit is contained in:
2026-09-11 14:47:03 +00:00
parent df9b7645d6
commit d7fac2d0e7
8 changed files with 100 additions and 12 deletions

View File

@@ -251,6 +251,9 @@ a.btn{text-decoration:none;color:inherit}
.ep .file{display:flex;gap:6px;align-items:center;flex-wrap:wrap;color:var(--faint);font-size:11.5px}
.ep .file .dlbar{flex-basis:100%;margin-top:2px}
.ep .rowacts{justify-content:flex-end}
/* One feed's own table has no need of a Feed column; All Subscriptions does. */
#split.one .ephead,#split.one .ep{grid-template-columns:22px 22px minmax(120px,1fr) minmax(0,112px) minmax(0,92px) 64px}
#split.one .h-fd,#split.one .ep .fd{display:none}
.dot{width:3px;height:3px;border-radius:50%;background:var(--faint);flex:none}
.chip{
font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;font-weight:700;
@@ -350,8 +353,11 @@ input[type=range]::-moz-range-thumb{width:12px;height:12px;border:0;border-radiu
border-top:1px solid var(--line);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
/* The feed's own header, kept to one line so the table starts high, as it did. */
.fhead.slim{align-items:center;gap:12px;margin-bottom:10px}
.fhead.slim{align-items:center;gap:8px 12px;margin-bottom:10px;flex-wrap:wrap}
.fhead.slim .art{width:44px;height:44px;font-size:15px;box-shadow:none}
.fhead.slim .meta{flex:1 1 260px}
/* One line each, or a long title wraps to three and pushes the table down. */
.fhead.slim h2,.fhead.slim .sub.stat{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fhead.slim h2{font-size:17px}
.fhead.slim .sub{margin-bottom:0;font-size:12.5px}
.fhead.slim .acts{margin-top:0;flex:none}
@@ -387,7 +393,7 @@ input[type=range]::-moz-range-thumb{width:12px;height:12px;border:0;border-radiu
#split{grid-template-columns:1fr;grid-template-rows:1fr;grid-template-areas:"list"}
/* Title and date only; the files follow the item's text in the reader instead. */
#files,.ephead,.ep .fd,.ep .file,.ep .rowacts{display:none}
.ep{grid-template-columns:20px 20px minmax(0,1fr) auto}
.ep,#split.one .ep{grid-template-columns:20px 20px minmax(0,1fr) auto}
#grab{display:none}
#detail{position:fixed;inset:0;z-index:38;display:none;border-top:0;padding:12px 16px 90px}
body.reading #detail{display:block}
@@ -675,7 +681,7 @@ function renderFeed(){
${artHTML(f.image,name)}
<div class="meta">
<h2>${esc(name)}</h2>
<div class="sub">${f.entries} items · ${f.downloaded} downloaded · checked ${ago(f.last_checked)}
<div class="sub stat">${f.entries} items · ${f.downloaded} downloaded · checked ${ago(f.last_checked)}
· every ${everyText(f.every_mins)}${f.next_check?` · next ${due(f.next_check)}`:''}${
f.subscribers>1?` · shared with ${f.subscribers-1} other ${f.subscribers===2?'person':'people'}`:''}</div>
${f.last_error?`<div class="sub" style="color:var(--bad)">${esc(f.last_error)}</div>`:''}
@@ -708,8 +714,9 @@ function renderFeed(){
const pane=document.createElement('div');
pane.id='split';
if(f) pane.className='one';
pane.innerHTML='<div id="list"><div class="ephead"><span></span><span title="Kept">⚑</span>'+
'<span>Item</span><span>Feed</span><span>File</span><span>Published</span><span></span></div>'+
'<span>Item</span><span class="h-fd">Feed</span><span>File</span><span>Published</span><span></span></div>'+
'<div id="eps"></div></div><div id="files"></div><div id="grab"></div><div id="detail"></div>';
box.appendChild(pane);
pane.style.setProperty('--listh', localStorage.getItem('ipx.listh') || '40%');
@@ -823,6 +830,8 @@ function epEl(e){
const has=!!(enc&&enc.path);
const playable=isPlayable(enc);
const others=e.enclosures.length-1;
// A file deleted to save space reads as if it was never downloaded: no chip, just the ⤓.
const gone=!!enc&&enc.state==='reaped';
const el=document.createElement('div');
el.className='ep'+(e.read?' read':'')+(S.sel===e.guid?' sel':'')+
(player.guid===e.guid?' playing':'');
@@ -845,7 +854,7 @@ function epEl(e){
</div>
<span class="fd">${esc(feedName(e.feed_id))}</span>
<div class="file">
${enc?`<span class="chip ${esc(enc.state)}">${
${enc&&!gone?`<span class="chip ${esc(enc.state)}">${
has?'downloaded':(enc.state==='skipped'?kindOf(enc):esc(enc.state))}</span>`:''}
${enc&&enc.length?`<span>${mb(enc.length)}</span>`:''}
${enc&&!has?`<div class="dlbar" data-bar="${enc.id}"><i></i></div>`:''}
@@ -1022,8 +1031,9 @@ function encBox(x){
// straight to the publisher's copy in a new tab -- no download, and nothing proxied
// through here, which would make ipx a fetch-anything relay.
const viewable = !isPlayable(x) && x.state !== 'pending';
// A file deleted to save space reads as if it was never downloaded, so it gets no chip.
return `<div class="encbox">
<span class="chip ${esc(x.state)}">${x.state==='skipped'?kindOf(x):esc(x.state)}</span>
${x.state==='reaped'?'':`<span class="chip ${esc(x.state)}">${x.state==='skipped'?kindOf(x):esc(x.state)}</span>`}
<span class="meta" style="flex:1">${esc(kindOf(x))}${size?' \u00b7 '+size:''}</span>
${x.state==='error'&&x.last_error?`<span class="err">${esc(x.last_error)}</span>`:''}
${viewable?`<a class="btn" href="${esc(x.url)}" target="_blank" rel="noopener noreferrer">View</a>`:''}