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
@@ -46,13 +46,13 @@ function lvPlay(b, at) {
lvApi('/shelf', lvOpt('POST', { id: b.id })).catch(() => {}); // a book you start is kept on your shelf
}
async function viewFreeBooks() {
const v = $('#view'); v.innerHTML = '<div class="wrap"><h1>Free audiobooks</h1><p class="empty">Loading…</p></div>';
async function viewFreeBooks(host) { // host: { el, hash } when shown inside the Store tab
const v = host ? host.el : $('#view'), here = () => location.hash.startsWith(host ? host.hash : '#/audio/free'); v.innerHTML = '<div class="wrap"><h1>Free audiobooks</h1><p class="empty">Loading…</p></div>';
try { lvShelf = await lvApi('/shelf'); await lvLoadProg(); } catch (e) { v.innerHTML = `<div class="wrap"><a class="btn ghost" href="#/audio">${icon('back')}Audiobooks</a><p class="err">${esc(e.message)}</p></div>`; return; }
if (!location.hash.startsWith('#/audio/free')) return;
if (!here()) return;
const S = { q: '', page: 1, items: [], more: false, busy: false, seq: 0 };
const card = b => lvCard(b);
v.innerHTML = `<div class="wrap"><div class="ptop"><a class="btn ghost" href="#/audio">${icon('back')}Audiobooks</a><h1 style="margin:0">Free audiobooks</h1></div>
v.innerHTML = `<div class="wrap">${host ? '' : `<div class="ptop"><a class="btn ghost" href="#/audio">${icon('back')}Audiobooks</a><h1 style="margin:0">Free audiobooks</h1></div>`}
<p class="sub">Public-domain books read aloud by volunteers (LibriVox). Free, with no sign-in. A book you start is kept on your shelf.</p>
<input type="search" id="lvq" placeholder="Search by title or author" autocomplete="off" autocapitalize="off" style="width:100%"><div id="lvnote"></div><div id="lvbody"></div><div id="lvmore" class="bar" style="justify-content:center"></div></div>`;
const known = new Map(lvShelf.map(b => [b.id, b]));