Files
ipodderx-rs/web/index.html
rays 9dc4c1ddfa Add feed check scheduling, global with per-feed override
general.schedule and feeds.<id>.schedule take "every 30m", "4h", "1d" or
bare minutes. The legacy interval_mins is still read. An explicit per-feed
schedule wins over the publisher's ttl; without one, ttl still raises the
interval when they ask to be polled less often.

Fixes two bugs found while testing it:

null never cleared a field. serde maps JSON null onto the outer None of an
Option<Option<T>>, so "clear this" was indistinguishable from "not
supplied" and every clear silently no-opped with a 204.

The daemon ignored SIGTERM while working. select! races branches only at
selection time, so a signal queued behind an in-flight download and the
process had to be SIGKILLed. The stop signal now cancels work in progress:
SIGTERM mid-download exits in 1s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
2026-09-10 02:25:38 +00:00

799 lines
83 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark light">
<title>iPodderX</title>
<link rel="icon" href="data:image/png;base64,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">
<style>
/* Palette taken from the 2004 iPodderX icon: the silver device body, the blue
screen, and the amber EQ bars. Hex values in comments are sampled straight from it. */
:root {
--bg:#0e131b; /* the screen's navy (#314B74), taken right down */
--panel:#151c27;
--panel2:#1c2431;
--raise:#25303f;
--line:#2c3849;
--fg:#f5f5f5; /* #F5F5F5 device highlight */
--dim:#95a0b1; /* #95A0B1 straight from the icon's blue-grey */
--faint:#7a8799; /* lifted from the icon ramp until it clears AA at small sizes */
--accent:#92b2e6; /* #92B2E6 the screen blue */
--accent2:#f49e2c; /* #F49E2C the EQ bars */
--ink:#0e131b; /* text on an accent fill */
--good:#6fbf8b;
--warn:#f49e2c; /* the amber doubles as the pending colour */
--bad:#e2705f;
--shadow:0 8px 28px rgba(6,10,16,.55);
--r:10px;
}
:root[data-theme="light"] {
--bg:#f2f4f7;
--panel:#ffffff; /* #FFFFFF device body */
--panel2:#e9edf3;
--raise:#dde3ec;
--line:#d6d6d6; /* #D6D6D6 device edge */
--fg:#1a1a1a; /* #1A1A1A icon outline */
--dim:#606060; /* #606060 */
--faint:#767676; /* between the icon's #929292 and #606060, to clear AA */
--accent:#2d5391; /* #2D5391 the deep screen blue reads better on white */
--accent2:#b06f10;
--ink:#ffffff;
--good:#2f7d4f;
--warn:#b06f10;
--bad:#b3402f;
--shadow:0 8px 28px rgba(45,83,145,.14);
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;background:var(--bg);color:var(--fg);
font:14.5px/1.55 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
display:grid;grid-template-rows:1fr auto;height:100vh;overflow:hidden;
}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
a{color:var(--accent)}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-thumb{background:var(--raise);border-radius:6px}
::-webkit-scrollbar-track{background:transparent}
/* ---------- shell ---------- */
#shell{display:grid;grid-template-columns:290px 1fr;min-height:0;overflow:hidden}
#sidebar{
background:var(--panel);border-right:1px solid var(--line);
display:flex;flex-direction:column;min-height:0;
}
.brand{display:flex;align-items:center;gap:9px;padding:14px 14px 10px}
.brand .logo{
width:30px;height:30px;flex:none;object-fit:contain;
}
.brand h1{font-size:16px;margin:0;font-weight:650;letter-spacing:-.01em;color:var(--fg);flex:1}
.iconbtn{
width:30px;height:30px;border-radius:8px;display:grid;place-items:center;
color:var(--dim);flex:none;
}
.iconbtn:hover{background:var(--raise);color:var(--fg)}
.sidetools{display:flex;gap:6px;padding:0 12px 10px}
.sidetools button{
flex:1;background:var(--panel2);border:1px solid var(--line);border-radius:8px;
padding:6px 8px;font-size:12.5px;color:var(--dim);
}
.sidetools button:hover{border-color:var(--accent);color:var(--fg)}
.searchwrap{padding:0 12px 8px}
input[type=search],input[type=text],input[type=number],select{
width:100%;background:var(--bg);border:1px solid var(--line);color:var(--fg);
border-radius:8px;padding:7px 10px;font:inherit;font-size:13.5px;
}
input:focus,select:focus{outline:0;border-color:var(--accent)}
#feedlist{overflow-y:auto;padding:0 8px 12px;flex:1;min-height:0}
.feed{
display:flex;gap:10px;align-items:center;padding:7px 8px;border-radius:9px;
cursor:pointer;margin-bottom:1px;
}
.feed:hover{background:var(--panel2)}
.feed.sel{background:var(--raise)}
.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;
}
.feed .art{width:38px;height:38px;font-size:14px}
.feed .txt{min-width:0;flex:1}
.feed .txt b{display:block;font-weight:600;font-size:13.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.feed .txt small{color:var(--faint);font-size:11.5px}
.badge{
background:var(--accent);color:var(--ink);border-radius:20px;padding:1px 7px;
font-size:11px;font-weight:700;flex:none;
}
.badge.zero{background:var(--raise);color:var(--faint)}
/* ---------- main ---------- */
#main{overflow-y:auto;min-height:0;scroll-behavior:smooth}
.wrap{max-width:1000px;margin:0 auto;padding:18px 22px 40px}
.fhead{display:flex;gap:18px;margin-bottom:18px}
.fhead .art{width:118px;height:118px;font-size:34px;box-shadow:var(--shadow)}
.fhead .meta{min-width:0;flex:1;display:flex;flex-direction:column}
.fhead h2{margin:0 0 3px;font-size:23px;line-height:1.2}
.fhead .sub{color:var(--dim);font-size:13px;margin-bottom:8px}
.fhead .sub a{color:var(--dim);text-decoration:none}
.fhead .sub a:hover{color:var(--accent)}
.acts{display:flex;gap:7px;flex-wrap:wrap;margin-top:auto}
.btn{
background:var(--panel2);border:1px solid var(--line);border-radius:8px;
padding:6px 12px;font-size:13px;
}
.btn:hover{border-color:var(--accent)}
.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)}
.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;
}
.tabs{display:flex;gap:2px;background:var(--panel2);border-radius:9px;padding:3px}
.tabs button{padding:5px 12px;border-radius:7px;font-size:13px;color:var(--dim)}
.tabs button.on{background:var(--raise);color:var(--fg);font-weight:600}
.toolbar .grow{flex:1;min-width:150px;max-width:320px}
/* ---------- episodes ---------- */
.ep{
display:flex;gap:12px;padding:11px;border-radius:var(--r);
border:1px solid transparent;margin-bottom:3px;position:relative;
}
.ep:hover{background:var(--panel)}
.ep.playing{background:var(--panel);border-color:var(--accent)}
.ep .art{width:52px;height:52px;font-size:16px;cursor:pointer;position:relative}
.ep .art .ovl{
position:absolute;inset:0;display:grid;place-items:center;
background:rgba(0,0,0,.5);opacity:0;transition:opacity .12s;color:#fff;
}
.ep .art:hover .ovl,.ep.playing .art .ovl{opacity:1}
.ep .body{flex:1;min-width:0}
.ep .t{font-weight:600;font-size:14.5px;cursor:pointer;display:block}
.ep.read .t{color:var(--dim);font-weight:500}
.ep .line{display:flex;gap:9px;align-items:center;flex-wrap:wrap;color:var(--faint);font-size:12px;margin-top:2px}
.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;
padding:1px 6px;border-radius:5px;background:var(--raise);color:var(--dim);
}
.chip.done{background:color-mix(in srgb,var(--good) 16%,transparent);color:var(--good)}
.chip.error{background:color-mix(in srgb,var(--bad) 16%,transparent);color:var(--bad)}
.chip.pending{background:color-mix(in srgb,var(--warn) 16%,transparent);color:var(--warn)}
.chip.new{background:var(--accent);color:var(--ink)}
.ep .rowacts{display:flex;gap:2px;align-items:flex-start;opacity:0;transition:opacity .12s}
.ep:hover .rowacts,.ep.open .rowacts{opacity:1}
.notes{
margin-top:10px;padding:11px 13px;background:var(--panel2);border-radius:9px;
color:var(--dim);font-size:13.5px;overflow-wrap:anywhere;
}
.notes img{max-width:100%;height:auto;border-radius:6px}
.notes p:first-child{margin-top:0}.notes p:last-child{margin-bottom:0}
.dlbar{height:3px;background:var(--raise);border-radius:2px;overflow:hidden;margin-top:7px}
.dlbar i{display:block;height:100%;width:0;background:var(--accent);transition:width .25s}
.empty{color:var(--faint);text-align:center;padding:50px 0}
#more{display:block;width:100%;margin-top:10px}
/* ---------- player ---------- */
#player{
border-top:1px solid var(--line);background:var(--panel);
display:none;grid-template-columns:auto 1fr auto;gap:14px;align-items:center;
padding:9px 16px;box-shadow:0 -6px 24px rgba(6,10,16,.4);
}
#player.on{display:grid}
#pnow{display:flex;gap:11px;align-items:center;min-width:0;width:250px}
#pnow .art{width:44px;height:44px;font-size:13px}
#pnow .txt{min-width:0}
#pnow b{display:block;font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#pnow small{color:var(--faint);font-size:11.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}
#pmid{display:flex;flex-direction:column;gap:3px;min-width:0}
#pbtns{display:flex;gap:5px;align-items:center;justify-content:center}
#pbtns .iconbtn{width:34px;height:34px}
#pplay{
width:40px!important;height:40px!important;background:var(--fg);color:var(--bg);
border-radius:50%;
}
#pplay:hover{background:var(--accent);color:var(--ink)}
#seekrow{display:flex;gap:9px;align-items:center;font-variant-numeric:tabular-nums;font-size:11.5px;color:var(--faint)}
input[type=range]{
-webkit-appearance:none;appearance:none;height:4px;border-radius:3px;flex:1;
background:var(--raise);cursor:pointer;
}
input[type=range]::-webkit-slider-thumb{
-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:var(--accent);
}
input[type=range]::-moz-range-thumb{width:12px;height:12px;border:0;border-radius:50%;background:var(--accent)}
#pright{display:flex;gap:8px;align-items:center}
#pright select{width:auto;padding:4px 6px;font-size:12px}
#vol{width:78px;flex:none}
/* ---------- overlays ---------- */
#modal{
position:fixed;inset:0;background:rgba(0,0,0,.6);display:none;
place-items:center;z-index:50;padding:20px;
}
#modal.on{display:grid}
.card{
background:var(--panel);border:1px solid var(--line);border-radius:14px;
padding:20px;width:min(460px,100%);box-shadow:var(--shadow);max-height:88vh;overflow:auto;
}
.card h3{margin:0 0 14px;font-size:17px}
.field{display:grid;gap:4px;margin-bottom:12px}
.field label{font-size:12px;color:var(--dim)}
.field .hint{font-size:11.5px;color:var(--faint)}
.check{display:flex;gap:8px;align-items:center;font-size:13.5px;margin-bottom:9px}
.inline{display:flex;gap:6px;align-items:stretch}
.inline input{flex:1;min-width:0}
.inline .btn{white-space:nowrap;flex:none}
.check input{width:16px;height:16px;accent-color:var(--accent)}
.cardacts{display:flex;gap:8px;justify-content:flex-end;margin-top:16px}
#toasts{position:fixed;bottom:88px;right:18px;display:flex;flex-direction:column;gap:8px;z-index:60}
.toast{
background:var(--raise);border:1px solid var(--line);border-radius:9px;
padding:9px 13px;font-size:13px;box-shadow:var(--shadow);animation:in .18s;max-width:340px;
}
.toast.bad{border-color:var(--bad);color:var(--bad)}
@keyframes in{from{opacity:0;transform:translateY(6px)}}
#burger{display:none}
@media (max-width:820px){
#shell{grid-template-columns:1fr}
#sidebar{position:fixed;inset:0 auto 0 0;width:280px;z-index:40;transform:translateX(-100%);transition:transform .2s;box-shadow:var(--shadow)}
#sidebar.open{transform:none}
#burger{display:grid}
.fhead .art{width:78px;height:78px;font-size:24px}
.fhead h2{font-size:19px}
#pnow{width:auto;flex:1}
#pnow .txt small,#pright #vol{display:none}
.wrap{padding:14px}
}
</style>
</head>
<body>
<div id="shell">
<aside id="sidebar">
<div class="brand">
<img class="logo" src="data:image/png;base64,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" alt="iPodderX" title="The original iPodderX icon, 2004"><h1>iPodderX</h1>
<button class="iconbtn" id="prefs" title="Settings"></button>
<button class="iconbtn" id="theme" title="Toggle theme"></button>
</div>
<div class="sidetools">
<button id="addFeed">+ Feed</button>
<button id="scanAll">Scan all</button>
<button id="opml" title="Import / export OPML">OPML</button>
</div>
<div class="searchwrap"><input type="search" id="feedFilter" placeholder="Filter feeds…"></div>
<div id="feedlist"></div>
</aside>
<div id="main"><div class="wrap" id="content"></div></div>
</div>
<div id="player">
<div id="pnow">
<button class="iconbtn" id="burger" title="Feeds"></button>
<div id="partwrap"></div>
<div class="txt"><b id="ptitle"></b><small id="pfeed"></small></div>
</div>
<div id="pmid">
<div id="pbtns">
<button class="iconbtn" id="pback" title="Back 15s (←)">↺15</button>
<button class="iconbtn" id="pplay" title="Play/pause (space)"></button>
<button class="iconbtn" id="pfwd" title="Forward 30s (→)">30↻</button>
</div>
<div id="seekrow">
<span id="pcur">0:00</span>
<input type="range" id="seek" min="0" max="1000" value="0">
<span id="pdur">0:00</span>
</div>
</div>
<div id="pright">
<select id="rate" title="Speed">
<option value="0.8">0.8×</option><option value="1" selected>1×</option>
<option value="1.25">1.25×</option><option value="1.5">1.5×</option>
<option value="1.75">1.75×</option><option value="2">2×</option><option value="2.5">2.5×</option>
</select>
<input type="range" id="vol" min="0" max="100" value="100" title="Volume">
<button class="iconbtn" id="pclose" title="Close"></button>
</div>
</div>
<div id="modal"><div class="card" id="modalCard"></div></div>
<div id="toasts"></div>
<audio id="audio" preload="metadata"></audio>
<script>
'use strict';
const $ = (s,r=document)=>r.querySelector(s);
const $$ = (s,r=document)=>[...r.querySelectorAll(s)];
const esc = s => (s??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
async function api(url,opts){
const r = await fetch(url,{headers:{'Content-Type':'application/json'},...opts});
if(!r.ok) throw new Error(await r.text().catch(()=>r.status)||r.status);
return r.status===204?null:r.json().catch(()=>null);
}
// navigator.clipboard only exists in a secure context. Served over plain HTTP on a LAN
// address it is undefined, so fall back to the old selection-based copy.
async function copyText(text,btn){
const flash=ok=>{
if(!btn) return;
const was=btn.textContent;
btn.textContent=ok?'Copied':'Failed';
setTimeout(()=>btn.textContent=was,1300);
};
try{
if(navigator.clipboard&&window.isSecureContext){
await navigator.clipboard.writeText(text);
}else{
const ta=document.createElement('textarea');
ta.value=text; ta.setAttribute('readonly','');
ta.style.cssText='position:fixed;top:-1000px;opacity:0';
document.body.appendChild(ta);
ta.select(); ta.setSelectionRange(0,ta.value.length);
const ok=document.execCommand('copy');
ta.remove();
if(!ok) throw new Error('copy rejected');
}
flash(true);
}catch(e){
flash(false);
toast('Could not copy automatically — select the URL and copy it manually',true);
}
}
function toast(msg,bad){
const t=document.createElement('div');
t.className='toast'+(bad?' bad':''); t.textContent=msg;
$('#toasts').appendChild(t);
setTimeout(()=>{t.style.opacity=0;t.style.transition='opacity .3s';setTimeout(()=>t.remove(),320)},bad?6000:3200);
}
const clock = s => {
s=Math.max(0,Math.floor(s||0));
const h=Math.floor(s/3600),m=Math.floor(s%3600/60),x=s%60;
return h?`${h}:${String(m).padStart(2,'0')}:${String(x).padStart(2,'0')}`:`${m}:${String(x).padStart(2,'0')}`;
};
const ago = t => {
if(!t) return 'never';
const d=(Date.now()/1000)-t;
if(d<3600) return Math.max(1,Math.round(d/60))+'m ago';
if(d<86400) return Math.round(d/3600)+'h ago';
if(d<2592000) return Math.round(d/86400)+'d ago';
return new Date(t*1000).toLocaleDateString(undefined,{month:'short',day:'numeric',year:'numeric'});
};
const dateOf = t => t?new Date(t*1000).toLocaleDateString(undefined,{month:'short',day:'numeric',year:'numeric'}):'';
const mb = n => n?(n/1048576).toFixed(0)+' MB':'';
const initials = s => (s||'?').replace(/[^A-Za-z0-9 ]/g,'').split(/\s+/).filter(Boolean).slice(0,2).map(w=>w[0]).join('').toUpperCase()||'?';
function artHTML(url,name,cls){
return url
? `<img class="art ${cls||''}" src="${esc(url)}" alt="" loading="lazy" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'art ${cls||''}',textContent:'${esc(initials(name))}'}))">`
: `<div class="art ${cls||''}">${esc(initials(name))}</div>`;
}
/* ---------------- state ---------------- */
const S = {
feeds:[], feed:null, entries:[], total:0, offset:0, limit:50,
filter:'unread', q:'', open:new Set(), busy:new Set(),
};
const LIMIT = 50;
/* ---------------- feeds ---------------- */
async function loadFeeds(keepSel){
S.feeds = await api('/api/feeds');
api('/api/settings').then(g=>{globalEvery=g.every_mins}).catch(()=>{});
renderFeeds();
if(!keepSel && !S.feed && S.feeds.length) selectFeed(S.feeds[0].id);
}
function renderFeeds(){
const q=$('#feedFilter').value.trim().toLowerCase();
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; }
for(const f of shown){
const el=document.createElement('div');
el.className='feed'+(S.feed===f.id?' sel':'');
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>`+
`<span class="badge${f.unread?'':' zero'}">${f.unread}</span>`;
el.onclick=()=>{ selectFeed(f.id); $('#sidebar').classList.remove('open'); };
list.appendChild(el);
}
}
function selectFeed(id){
S.feed=id; S.offset=0; S.open.clear(); S.q='';
renderFeeds(); renderFeed(); loadEntries();
}
/* ---------------- feed page ---------------- */
function renderFeed(){
const f=S.feeds.find(x=>x.id===S.feed);
if(!f){ $('#content').innerHTML='<p class="empty">Add a feed to get started.</p>'; return; }
$('#content').innerHTML = `
<div class="fhead">
${artHTML(f.image,f.title||f.id)}
<div class="meta">
<h2>${esc(f.title||f.id)}</h2>
<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>`:''}
<div class="acts">
<button class="btn primary" data-a="scan">Scan now</button>
<button class="btn" data-a="dl">Download latest…</button>
<button class="btn" data-a="read">Mark all read</button>
<button class="btn" data-a="settings">Settings</button>
<button class="btn danger" data-a="rm">Unsubscribe</button>
</div>
</div>
</div>
<div class="toolbar">
<div class="tabs">
${['unread','all','downloaded','flagged'].map(t=>
`<button data-f="${t}" class="${S.filter===t?'on':''}">${t[0].toUpperCase()+t.slice(1)}</button>`).join('')}
</div>
<input type="search" class="grow" id="epSearch" placeholder="Search episodes…" value="${esc(S.q)}">
<span style="color:var(--faint);font-size:12.5px" id="count"></span>
</div>
<div id="eps"></div>`;
$$('#content .acts .btn').forEach(b=>b.onclick=()=>feedAction(b.dataset.a,f));
$$('#content .tabs button').forEach(b=>b.onclick=()=>{S.filter=b.dataset.f;S.offset=0;renderFeed();loadEntries()});
let t; $('#epSearch').oninput=e=>{clearTimeout(t);t=setTimeout(()=>{S.q=e.target.value;S.offset=0;loadEntries()},250)};
}
async function feedAction(a,f){
if(a==='scan'){ toast('Scanning '+(f.title||f.id)+'…'); await api('/api/fetch',{method:'POST',body:JSON.stringify({feed:f.id,force:true})}); }
if(a==='read'){ const r=await api(`/api/feeds/${encodeURIComponent(f.id)}/read-all`,{method:'POST'}); toast(`Marked ${r.marked} read`); await loadFeeds(true); loadEntries(); }
if(a==='rm') removeFeed(f);
if(a==='settings') settingsModal(f);
if(a==='dl') downloadLatestModal(f);
}
/* ---------------- episodes ---------------- */
async function loadEntries(append){
if(!S.feed) return;
const p=new URLSearchParams({offset:S.offset,limit:LIMIT,filter:S.filter});
if(S.q) p.set('q',S.q);
const r=await api(`/api/feeds/${encodeURIComponent(S.feed)}/entries?${p}`);
S.total=r.total;
S.entries = append ? S.entries.concat(r.entries) : r.entries;
renderEntries();
}
function renderEntries(){
const box=$('#eps'); if(!box) return;
const c=$('#count'); if(c) c.textContent=`${S.total} episode${S.total===1?'':'s'}`;
box.innerHTML='';
if(!S.entries.length){
box.innerHTML=`<p class="empty">${S.q?'Nothing matches that search.':'Nothing here yet — try Scan now.'}</p>`;
return;
}
for(const e of S.entries) box.appendChild(epEl(e));
if(S.entries.length < S.total){
const b=document.createElement('button');
b.className='btn'; b.id='more'; b.textContent=`Load more (${S.entries.length} of ${S.total})`;
b.onclick=()=>{S.offset+=LIMIT;loadEntries(true)};
box.appendChild(b);
}
}
function epEl(e){
const enc=e.enclosures[0];
const has=!!(enc&&enc.path);
const el=document.createElement('div');
el.className='ep'+(e.read?' read':'')+(S.open.has(e.guid)?' open':'')+
(player.guid===e.guid?' playing':'');
el.dataset.guid=e.guid;
const num=[e.season?`S${e.season}`:'',e.episode?`E${e.episode}`:''].filter(Boolean).join('');
const left = e.position>10 && e.duration ? `${clock(e.duration-e.position)} left` : (e.duration?clock(e.duration):'');
el.innerHTML=`
${artHTML(e.image||feedArt(),e.title,'')}
<div class="body">
<span class="t">${esc(e.title||'(untitled)')}</span>
<div class="line">
${e.read?'':'<span class="chip new">new</span>'}
${num?`<span>${num}</span><span class="dot"></span>`:''}
<span>${dateOf(e.published)}</span>
${left?'<span class="dot"></span><span>'+left+'</span>':''}
${enc?`<span class="dot"></span><span class="chip ${esc(enc.state)}">${has?'downloaded':esc(enc.state)}</span>`:''}
${enc&&enc.length?`<span>${mb(enc.length)}</span>`:''}
${e.flagged?'<span class="dot"></span><span>★ kept</span>':''}
</div>
${enc&&!has?`<div class="dlbar" data-bar="${enc.id}"><i></i></div>`:''}
${enc&&enc.last_error?`<div class="line" style="color:var(--bad)">${esc(enc.last_error)}</div>`:''}
</div>
<div class="rowacts">
${has?`<button class="iconbtn" data-a="play" title="Play">▶</button>`:
(enc?`<button class="iconbtn" data-a="get" title="Download">⤓</button>`:'')}
<button class="iconbtn" data-a="flag" title="${e.flagged?'Stop keeping':'Keep (never auto-delete)'}">${e.flagged?'★':'☆'}</button>
<button class="iconbtn" data-a="read" title="Mark ${e.read?'unread':'read'}">${e.read?'○':'●'}</button>
${has?`<button class="iconbtn" data-a="del" title="Delete file">🗑</button>`:''}
<button class="iconbtn" data-a="notes" title="Show notes">≡</button>
</div>`;
const art=$('.art',el);
if(art&&has) art.insertAdjacentHTML('beforeend','<span class="ovl">▶</span>');
if(art&&has) art.onclick=()=>play(e);
$('.t',el).onclick=()=>toggleNotes(e,el);
$$('.rowacts .iconbtn',el).forEach(b=>b.onclick=ev=>{ev.stopPropagation();epAction(b.dataset.a,e,el)});
if(S.open.has(e.guid)) showNotes(e,el);
return el;
}
function feedArt(){ const f=S.feeds.find(x=>x.id===S.feed); return f&&f.image; }
function toggleNotes(e,el){
if(S.open.has(e.guid)){ S.open.delete(e.guid); const n=$('.notes',el); if(n)n.remove(); el.classList.remove('open'); }
else { S.open.add(e.guid); showNotes(e,el); el.classList.add('open'); }
}
function showNotes(e,el){
if($('.notes',el)) return;
const d=document.createElement('div');
d.className='notes';
// Sanitized server-side with ammonia before it ever reaches the browser.
d.innerHTML=(e.description&&e.description.trim())||'<em>No show notes.</em>';
if(e.link) d.insertAdjacentHTML('beforeend',`<p><a href="${esc(e.link)}" target="_blank" rel="noopener noreferrer">Open original ↗</a></p>`);
$('.body',el).appendChild(d);
}
async function epAction(a,e,el){
const enc=e.enclosures[0];
const path=`/api/entries/${encodeURIComponent(e.feed_id)}/${encodeURIComponent(e.guid)}`;
try{
if(a==='play') play(e);
if(a==='notes') toggleNotes(e,el);
if(a==='flag'){ e.flagged=!e.flagged; await api(path+'/flags',{method:'POST',body:JSON.stringify({flagged:e.flagged})}); el.replaceWith(epEl(e)); }
if(a==='read'){ e.read=!e.read; await api(path+'/flags',{method:'POST',body:JSON.stringify({read:e.read})}); el.replaceWith(epEl(e)); loadFeeds(true); }
if(a==='get'){
if(!enc) return;
S.busy.add(enc.id);
await api(`/api/enclosures/${enc.id}/download`,{method:'POST'});
toast('Queued: '+(e.title||'episode'));
}
if(a==='del'){
if(!confirm('Delete the downloaded file?\n\nThe episode stays listed and will not be downloaded again automatically.')) return;
await api(`/api/enclosures/${enc.id}`,{method:'DELETE'});
toast('Deleted'); loadEntries(); loadFeeds(true);
}
}catch(err){ toast(err.message,true); }
}
/* ---------------- player ---------------- */
const audio=$('#audio');
const player={guid:null,feed:null,entry:null,saveAt:0,marked:false};
// Marked read when an episode has actually been listened to -- at the end, or past 90%.
// NOT on play: doing that made the episode vanish from the default Unread list the instant
// you pressed play, which looks exactly like it went missing.
function markPlayed(){
if(!player.guid||player.marked) return;
player.marked=true;
const e=player.entry;
if(!e||e.read) return;
e.read=true;
api(`/api/entries/${encodeURIComponent(e.feed_id)}/${encodeURIComponent(e.guid)}/flags`,
{method:'POST',body:JSON.stringify({read:true})}).then(()=>loadFeeds(true)).catch(()=>{});
}
function play(e){
const enc=e.enclosures.find(x=>x.path);
if(!enc){ toast('Not downloaded yet',true); return; }
const resuming = player.guid===e.guid;
if(!resuming){
player.guid=e.guid; player.feed=e.feed_id; player.entry=e;
audio.src=`/media/${enc.id}`;
audio.currentTime=0;
if(e.position>5) audio.addEventListener('loadedmetadata',()=>{audio.currentTime=e.position},{once:true});
$('#partwrap').innerHTML=artHTML(e.image||feedArt(),e.title);
$('#ptitle').textContent=e.title||'(untitled)';
const f=S.feeds.find(x=>x.id===e.feed_id);
$('#pfeed').textContent=f?(f.title||f.id):'';
$('#player').classList.add('on');
mediaSession(e,f);
player.marked=false;
}
audio.play().catch(err=>toast('Playback failed: '+err.message,true));
renderEntries();
}
function mediaSession(e,f){
if(!('mediaSession' in navigator)) return;
navigator.mediaSession.metadata=new MediaMetadata({
title:e.title||'', artist:f?(f.title||f.id):'', album:f?(f.title||''):'',
artwork:(e.image||(f&&f.image))?[{src:e.image||f.image,sizes:'512x512'}]:[],
});
const h={play:()=>audio.play(),pause:()=>audio.pause(),
seekbackward:()=>audio.currentTime-=15,seekforward:()=>audio.currentTime+=30};
for(const k in h){ try{navigator.mediaSession.setActionHandler(k,h[k])}catch{} }
}
audio.addEventListener('timeupdate',()=>{
const d=audio.duration||player.entry?.duration||0;
$('#pcur').textContent=clock(audio.currentTime);
$('#pdur').textContent=clock(d);
if(d) $('#seek').value=String(Math.round(audio.currentTime/d*1000));
// Persist roughly every 10s so a reload resumes where you were.
if(player.guid && audio.currentTime-player.saveAt>10){ savePos(); }
if(d && audio.currentTime/d >= 0.9) markPlayed();
});
function savePos(){
if(!player.guid) return;
player.saveAt=audio.currentTime;
if(player.entry) player.entry.position=Math.floor(audio.currentTime);
navigator.sendBeacon?.(
`/api/entries/${encodeURIComponent(player.feed)}/${encodeURIComponent(player.guid)}/position`,
new Blob([JSON.stringify({secs:Math.floor(audio.currentTime)})],{type:'application/json'}));
}
audio.addEventListener('pause',savePos);
audio.addEventListener('ended',()=>{savePos();markPlayed();$('#pplay').textContent='▶'});
audio.addEventListener('play',()=>$('#pplay').textContent='❚❚');
audio.addEventListener('pause',()=>$('#pplay').textContent='▶');
window.addEventListener('beforeunload',savePos);
$('#pplay').onclick=()=>audio.paused?audio.play():audio.pause();
$('#pback').onclick=()=>audio.currentTime-=15;
$('#pfwd').onclick=()=>audio.currentTime+=30;
$('#seek').oninput=e=>{const d=audio.duration;if(d)audio.currentTime=d*e.target.value/1000};
$('#rate').onchange=e=>{audio.playbackRate=+e.target.value;localStorage.setItem('ipx.rate',e.target.value)};
$('#vol').oninput=e=>{audio.volume=e.target.value/100;localStorage.setItem('ipx.vol',e.target.value)};
$('#pclose').onclick=()=>{savePos();audio.pause();audio.removeAttribute('src');player.guid=null;$('#player').classList.remove('on');renderEntries()};
(function restore(){
const r=localStorage.getItem('ipx.rate'), v=localStorage.getItem('ipx.vol');
if(r){$('#rate').value=r;audio.playbackRate=+r}
if(v){$('#vol').value=v;audio.volume=v/100}
})();
document.addEventListener('keydown',ev=>{
if(/^(INPUT|TEXTAREA|SELECT)$/.test(ev.target.tagName)) return;
if(ev.key===' '&&player.guid){ev.preventDefault();audio.paused?audio.play():audio.pause()}
else if(ev.key==='ArrowLeft'&&player.guid){audio.currentTime-=15}
else if(ev.key==='ArrowRight'&&player.guid){audio.currentTime+=30}
else if(ev.key==='/'){ev.preventDefault();$('#epSearch')?.focus()}
else if(ev.key==='Escape'){closeModal();$('#sidebar').classList.remove('open')}
});
/* ---------------- modals ---------------- */
function openModal(html){ $('#modalCard').innerHTML=html; $('#modal').classList.add('on'); }
function closeModal(){ $('#modal').classList.remove('on'); }
$('#modal').onclick=e=>{ if(e.target.id==='modal') closeModal(); };
$('#addFeed').onclick=()=>{
openModal(`<h3>Add a feed</h3>
<div class="field"><label>Feed URL</label><input type="text" id="nurl" placeholder="https://example.com/rss"></div>
<div class="field"><label>Folder (optional)</label><input type="text" id="nfolder" placeholder="Defaults to the feed title"></div>
<div class="field"><label>Keywords (optional, comma separated)</label>
<input type="text" id="nkw"><span class="hint">Only episodes matching a keyword are downloaded.</span></div>
<div class="cardacts"><button class="btn" onclick="closeModal()">Cancel</button>
<button class="btn primary" id="nsave">Add feed</button></div>`);
$('#nurl').focus();
$('#nsave').onclick=async()=>{
const url=$('#nurl').value.trim(); if(!url) return;
$('#nsave').textContent='Adding…'; $('#nsave').disabled=true;
try{
const r=await api('/api/feeds',{method:'POST',body:JSON.stringify({
url, folder:$('#nfolder').value.trim()||null,
keywords:$('#nkw').value.split(',').map(s=>s.trim()).filter(Boolean)})});
closeModal(); toast(r.existing?`Already subscribed as ${r.id}`:`Added ${r.id}`);
await loadFeeds(true); selectFeed(r.id);
}catch(e){ toast(e.message,true); $('#nsave').textContent='Add feed'; $('#nsave').disabled=false; }
};
};
function settingsModal(f){
openModal(`<h3>${esc(f.title||f.id)}</h3>
<div class="field"><label>Download folder</label>
<input type="text" id="sfolder" value="${esc(f.folder||'')}" placeholder="${esc(f.title||f.id)}"></div>
<div class="field"><label>Keywords</label>
<input type="text" id="skw" value="${esc(f.keywords.join(', '))}">
<span class="hint">Comma separated. Empty takes everything.</span></div>
<div class="field"><label>Check schedule</label>
<input type="text" id="ssched" value="${esc(f.schedule||'')}" placeholder="default — every ${everyText(globalEvery)}">
<span class="hint">e.g. <b>every 30m</b>, <b>every 6h</b>, <b>2d</b>. Empty follows the global
schedule. Setting one here overrides the feed's own suggested interval.</span></div>
<div class="field"><label>Max new downloads per scan</label>
<input type="number" id="smax" min="0" value="${f.max_new_per_check??''}">
<span class="hint">Blank means no limit. The rest wait for the next scan.</span></div>
<label class="check"><input type="checkbox" id="sauto" ${f.auto_download?'checked':''}> Download new episodes automatically</label>
<label class="check"><input type="checkbox" id="sexp" ${f.allow_explicit?'checked':''}> Allow episodes marked explicit</label>
<div class="field"><label>Feed URL</label>
<div class="inline">
<input type="text" id="surl" value="${esc(f.url)}" spellcheck="false">
<button type="button" class="btn" id="scopy">Copy</button>
</div>
<span class="hint">Editing this keeps every episode and download — handy when an auth token
in the URL is rotated. The feed is re-checked from scratch on the next scan.</span></div>
<div class="cardacts"><button class="btn" onclick="closeModal()">Cancel</button>
<button class="btn primary" id="ssave">Save</button></div>`);
$('#scopy').onclick=()=>copyText($('#surl').value,$('#scopy'));
$('#ssave').onclick=async()=>{
const max=$('#smax').value;
try{
await api(`/api/feeds/${encodeURIComponent(f.id)}`,{method:'PATCH',body:JSON.stringify({
url:$('#surl').value.trim(),
folder:$('#sfolder').value.trim()||null,
schedule:$('#ssched').value.trim()||null,
keywords:$('#skw').value.split(',').map(s=>s.trim()).filter(Boolean),
max_new_per_check:max===''?null:Number(max),
auto_download:$('#sauto').checked, allow_explicit:$('#sexp').checked})});
closeModal(); toast('Saved — applies on the next scan');
await loadFeeds(true); renderFeed(); loadEntries();
}catch(e){ toast(e.message,true); }
};
}
function downloadLatestModal(f){
openModal(`<h3>Download latest episodes</h3>
<div class="field"><label>How many of the newest undownloaded episodes?</label>
<input type="number" id="dcount" min="1" max="100" value="5">
<span class="hint">Queued immediately, ignoring the per-scan limit.</span></div>
<div class="cardacts"><button class="btn" onclick="closeModal()">Cancel</button>
<button class="btn primary" id="dgo">Download</button></div>`);
$('#dgo').onclick=async()=>{
const n=Number($('#dcount').value)||5;
try{
const r=await api(`/api/feeds/${encodeURIComponent(f.id)}/download-latest`,
{method:'POST',body:JSON.stringify({count:n})});
closeModal(); toast(`Queued ${r.queued} episode${r.queued===1?'':'s'}`);
}catch(e){ toast(e.message,true); }
};
}
function removeFeed(f){
openModal(`<h3>Unsubscribe?</h3>
<p style="color:var(--dim)">Removes <b>${esc(f.title||f.id)}</b> from your feeds.
Downloaded files and history are kept, so re-adding it will not pull the back catalogue again.</p>
<div class="cardacts"><button class="btn" onclick="closeModal()">Cancel</button>
<button class="btn danger" id="rgo">Unsubscribe</button></div>`);
$('#rgo').onclick=async()=>{
await api(`/api/feeds/${encodeURIComponent(f.id)}`,{method:'DELETE'});
closeModal(); toast('Unsubscribed'); S.feed=null;
await loadFeeds(); if(!S.feeds.length) renderFeed();
};
}
$('#opml').onclick=()=>{
openModal(`<h3>OPML</h3>
<p style="color:var(--dim);font-size:13.5px">Move subscriptions between podcast apps.</p>
<div class="cardacts" style="justify-content:flex-start">
<a class="btn" href="/api/opml" download="ipx-subscriptions.opml">Export</a>
</div>
<div class="field" style="margin-top:16px"><label>Import: paste OPML</label>
<textarea id="opmlText" rows="6" style="width:100%;background:var(--bg);border:1px solid var(--line);color:var(--fg);border-radius:8px;padding:8px;font:12px monospace"></textarea></div>
<div class="cardacts"><button class="btn" onclick="closeModal()">Close</button>
<button class="btn primary" id="oimp">Import</button></div>`);
$('#oimp').onclick=async()=>{
try{
const r=await api('/api/opml',{method:'POST',body:JSON.stringify({xml:$('#opmlText').value})});
closeModal(); toast(`Imported ${r.added} feed(s)`); loadFeeds(true);
}catch(e){ toast(e.message,true); }
};
};
$('#scanAll').onclick=async()=>{ toast('Scanning all feeds…'); await api('/api/fetch',{method:'POST',body:JSON.stringify({force:true})}); };
$('#prefs').onclick=prefsModal;
$('#feedFilter').oninput=renderFeeds;
$('#burger').onclick=()=>$('#sidebar').classList.toggle('open');
$('#theme').onclick=()=>{
const cur=document.documentElement.dataset.theme==='light'?'dark':'light';
document.documentElement.dataset.theme=cur; localStorage.setItem('ipx.theme',cur);
};
if(localStorage.getItem('ipx.theme')) document.documentElement.dataset.theme=localStorage.getItem('ipx.theme');
/* ---------------- live events ---------------- */
let sse;
function connect(){
sse=new EventSource('/api/events');
sse.onmessage=m=>{
let ev; try{ ev=JSON.parse(m.data) }catch{ return }
if(ev.ev==='progress'){
const pct=ev.total?ev.done/ev.total*100:0;
$$('.dlbar').forEach(b=>{ /* progress applies to whatever is downloading now */
if(b.dataset.bar) b.firstElementChild.style.width=pct+'%';
});
$('#count') && ($('#count').textContent=`downloading ${ev.file}${pct.toFixed(0)}%`);
}
else if(ev.ev==='download_done'){ toast('Downloaded '+ev.path.split('/').pop()); loadEntries(); loadFeeds(true); }
else if(ev.ev==='download_error'){ toast('Download failed: '+ev.msg,true); loadEntries(); }
else if(ev.ev==='feed_done'){ if(ev.new) toast(`${ev.feed}: ${ev.new} new`); loadFeeds(true); if(ev.feed===S.feed) loadEntries(); }
else if(ev.ev==='feed_error'){ toast(ev.feed+': '+ev.msg,true); loadFeeds(true); }
else if(ev.ev==='scan_done'){ loadFeeds(true); if(S.feed) loadEntries(); }
};
sse.onerror=()=>{ sse.close(); setTimeout(connect,4000); };
}
connect();
loadFeeds();
</script>
</body>
</html>