Store: one tab combining books, free audiobooks and radio, plus My Store for your own books, music, apps and sandboxed extensions
build-container / build (push) Successful in 7m39s

- Store tab with sections; the book store, LibriVox and radio finder are shown inside it (old entry points redirect)
- My Store: admin uploads files or copies from Seafile; books go to a person's shelf, music plays in the player, apps download
- Extensions: one HTML file run in a sandboxed frame with a small storage/toast/user bridge; example in docs/extensions

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-26 09:08:41 -04:00
1 parent 176b1b8169
commit f292294ad7
16 files changed
+439 -24

No files matched your search

+4 -4
View File
@@ -38,17 +38,17 @@ function rdoApplyNow(t, n) {
setInterval(rdoPoll, 10000); pl.a.addEventListener('playing', () => { if (rdoPlaying()) setTimeout(rdoPoll, 1500); });
function rdoDrawNow(el, n) { el.innerHTML = n && n.title ? `<small>Now playing</small><b>${esc(n.title)}</b>${n.artist ? '<span> · ' + esc(n.artist) + '</span>' : ''}` : '<small>Live</small>'; }
async function viewRadio() {
const v = $('#view'); v.innerHTML = '<div class="wrap"><h1>Radio</h1><p class="empty">Loading…</p></div>';
async function viewRadio(host) { // host: { el, hash } when shown inside the Store tab
const v = host ? host.el : $('#view'), here = () => location.hash.startsWith(host ? host.hash : '#/radio'); v.innerHTML = '<div class="wrap"><h1>Radio</h1><p class="empty">Loading…</p></div>';
let feat, mine;
try { ({ featured: feat, mine } = await rdoApi('/stations')); } catch (e) { v.innerHTML = `<div class="wrap"><h1>Radio</h1><p class="err">${esc(e.message)}</p></div>`; return; }
if (!location.hash.startsWith('#/radio')) return;
if (!here()) return;
rdoSaved = mine; const S = { q: '', tag: 'christian', items: [], more: false, busy: false, seq: 0, off: 0 };
const saved = id => rdoSaved.some(x => x.id === id);
const row = (s, fav) => `<div class="row rdorow${rdoPlaying() && rdoPlaying().key === 'radio:' + s.id ? ' playing' : ''}" data-id="${esc(s.id)}"><button class="rdoplay" data-play aria-label="Play ${esc(s.name)}"><span class="art">${s.favicon ? `<img alt="" loading="lazy" src="${esc(rdoIcon(s))}" onerror="this.remove()">` : ''}</span>
<span class="grow"><b>${esc(s.name)}</b><small>${esc(rdoMeta(s))}</small></span>${icon('play')}</button>${s.featured ? '' : `<button class="ibtn" data-fav aria-label="${saved(s.id) ? 'Remove from my stations' : 'Save to my stations'}" title="${saved(s.id) ? 'Remove from my stations' : 'Save to my stations'}" style="${saved(s.id) ? 'color:var(--danger)' : ''}">${icon('heart')}</button>`}</div>`;
const j = feat[0];
v.innerHTML = `<div class="wrap"><h1>Radio</h1>
v.innerHTML = `<div class="wrap">${host ? '' : '<h1>Radio</h1>'}
<div class="card rdofeat" style="max-width:none"><div class="sub" style="margin:0">Featured</div><h2 style="margin:2px 0 0;font-size:22px;text-transform:none;letter-spacing:0;color:var(--text)">${esc(j.name)}</h2>
<p class="sub" style="margin:2px 0 10px">${esc(rdoMeta(j))}</p><div id="rdonow" data-id="${esc(j.id)}" class="rdonow"><small>Live</small></div>
<div class="bar"><button class="btn" id="rdojp">${icon('play')}Listen live</button><button class="btn ghost" id="rdojl">${icon('lyrics')}Lyrics</button><button class="btn ghost" id="rdojv">Visuals</button><a class="btn ghost" href="${esc(j.homepage)}" target="_blank" rel="noopener">${icon('ext')}Website</a><button class="btn ghost" id="rdojr">Recently played</button></div></div>