Subscribe to an OPML, not just import one
A feed whose body sniffs as OPML is treated as a subscription list and re-read on every scan, as iPodderX did. Listed feeds become real config entries grouped under it, inherit its settings, land in one nested folder, and are scanned in the same run. When a feed leaves the OPML: removed if nothing was downloaded, kept and flagged otherwise, so a downloaded file is never orphaned. folder_for sanitized the whole folder string and would have flattened the nesting; each segment is sanitized separately now, and a traversal still cannot escape the download directory. Db::memory() also runs migrate(), which it did not, so a migration-only column passed tests while missing in production. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
This commit is contained in:
@@ -92,6 +92,13 @@ input:focus,select:focus{outline:0;border-color:var(--accent)}
|
||||
}
|
||||
.feed:hover{background:var(--panel2)}
|
||||
.feed.sel{background:var(--raise)}
|
||||
.feed.child{margin-left:14px}
|
||||
.feed.child .art{width:28px;height:28px;font-size:11px}
|
||||
.feed.group>.txt>b::after{content:" ⌄";color:var(--faint);font-weight:400}
|
||||
.tag{
|
||||
font-size:10px;text-transform:uppercase;letter-spacing:.04em;font-weight:700;
|
||||
padding:1px 5px;border-radius:4px;background:var(--raise);color:var(--warn);flex:none;
|
||||
}
|
||||
.art{
|
||||
border-radius:7px;object-fit:cover;background:var(--raise);flex:none;
|
||||
display:grid;place-items:center;color:var(--faint);font-weight:700;overflow:hidden;
|
||||
@@ -404,11 +411,24 @@ function renderFeeds(){
|
||||
const list=$('#feedlist'); list.innerHTML='';
|
||||
const shown=S.feeds.filter(f=>!q||(f.title||f.id).toLowerCase().includes(q));
|
||||
if(!shown.length){ list.innerHTML='<p class="empty" style="padding:20px 8px">No feeds.</p>'; return; }
|
||||
|
||||
// Feeds from a subscribed OPML sit under it, so the group reads as one thing.
|
||||
const byId=Object.fromEntries(shown.map(f=>[f.id,f]));
|
||||
const order=[];
|
||||
for(const f of shown){
|
||||
if(f.group && byId[f.group]) continue; // drawn under its parent instead
|
||||
order.push([f,0]);
|
||||
for(const c of shown) if(c.group===f.id) order.push([c,1]);
|
||||
}
|
||||
for(const [f,depth] of order){
|
||||
const kids=shown.filter(c=>c.group===f.id).length;
|
||||
const el=document.createElement('div');
|
||||
el.className='feed'+(S.feed===f.id?' sel':'');
|
||||
el.className='feed'+(S.feed===f.id?' sel':'')+(depth?' child':'')+(kids?' group':'');
|
||||
el.innerHTML = artHTML(f.image,f.title||f.id)+
|
||||
`<div class="txt"><b>${esc(f.title||f.id)}</b><small>${f.entries} eps · ${f.downloaded} saved</small></div>`+
|
||||
`<div class="txt"><b>${esc(f.title||f.id)}</b><small>`+
|
||||
(kids?`${kids} feed${kids===1?'':'s'}`:`${f.entries} eps · ${f.downloaded} saved`)+
|
||||
`</small></div>`+
|
||||
(f.orphaned?'<span class="tag" title="No longer listed in the OPML, kept because it has downloads">gone</span>':'')+
|
||||
`<span class="badge${f.unread?'':' zero'}">${f.unread}</span>`;
|
||||
el.onclick=()=>{ selectFeed(f.id); $('#sidebar').classList.remove('open'); };
|
||||
list.appendChild(el);
|
||||
@@ -431,6 +451,9 @@ function renderFeed(){
|
||||
<div class="sub">${f.entries} episodes · ${f.downloaded} downloaded · checked ${ago(f.last_checked)}
|
||||
· every ${everyText(f.every_mins)}${f.next_check?` · next ${due(f.next_check)}`:''}</div>
|
||||
${f.last_error?`<div class="sub" style="color:var(--bad)">${esc(f.last_error)}</div>`:''}
|
||||
${f.orphaned?`<div class="sub" style="color:var(--warn)">This feed is no longer listed in its
|
||||
OPML subscription. It was kept rather than removed because it has downloaded episodes.</div>`:''}
|
||||
${f.group?`<div class="sub">From the OPML subscription <b>${esc(f.group)}</b></div>`:''}
|
||||
<div class="acts">
|
||||
<button class="btn primary" data-a="scan">Scan now</button>
|
||||
<button class="btn" data-a="dl">Download latest…</button>
|
||||
|
||||
Reference in New Issue
Block a user