My Store: Add many books, with title, author, description and cover read out of each EPUB
build-container / build (push) Successful in 3m32s
build-container / build (push) Successful in 3m32s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
272760c321
commit
3376499805
5 files changed
+98
-1
No files matched your search
+23
-1
@@ -35,13 +35,14 @@ async function stMine(el) {
|
||||
const draw = () => {
|
||||
if (!$('#stbody')) return;
|
||||
const mine = stItems.filter(i => i.kind === 'extension' && stInstalled.includes(i.id)), shown = stItems.filter(i => stFilter === 'all' || i.kind === stFilter);
|
||||
el.innerHTML = `<div class="bar" style="margin:0 0 8px;justify-content:flex-start"><p class="sub" style="margin:0;flex:1">Books, music, apps and extensions added just for this hub.</p>${stAdmin ? `<button class="btn" id="stadd">${icon('plus')}Add</button>` : ''}</div>
|
||||
el.innerHTML = `<div class="bar" style="margin:0 0 8px;justify-content:flex-start"><p class="sub" style="margin:0;flex:1">Books, music, apps and extensions added just for this hub.</p>${stAdmin ? `<label class="btn ghost" style="cursor:pointer;margin:0 6px 0 0" title="Pick many EPUB, PDF, MOBI, AZW3 or DOCX files at once. Titles, authors and covers are read from the books.">${icon('plus')}Add many books<input type="file" id="stbulk" hidden multiple accept=".epub,.pdf,.mobi,.azw3,.azw,.docx"></label><button class="btn" id="stadd">${icon('plus')}Add</button>` : ''}</div>
|
||||
${mine.length ? `<h2>My extensions</h2><div class="vgrid">${mine.map(i => stCard(i, true)).join('')}</div>` : ''}
|
||||
<div class="seg" id="stkinds"><a href="#" data-k="all" class="${stFilter === 'all' ? 'on' : ''}">All</a>${ST_KINDS.map(k => `<a href="#" data-k="${k[0]}" class="${stFilter === k[0] ? 'on' : ''}">${k[1]}</a>`).join('')}</div>
|
||||
${shown.length ? `<div class="vgrid">${shown.map(i => stCard(i)).join('')}</div>` : `<p class="empty">${stItems.length ? 'Nothing of that kind yet.' : stAdmin ? 'Nothing here yet. Add your first book, album, app or extension.' : 'Nothing has been added yet.'}</p>`}`;
|
||||
el.querySelectorAll('#stkinds a').forEach(a => a.onclick = e => { e.preventDefault(); stFilter = a.dataset.k; draw(); });
|
||||
el.querySelectorAll('[data-it]').forEach(a => a.onclick = e => { e.preventDefault(); const it = stItems.find(x => x.id === a.dataset.it); if (a.dataset.run) location.hash = '#/store/ext/' + it.id; else stDetail(it, draw); });
|
||||
if ($('#stadd')) $('#stadd').onclick = () => stEdit(null, draw);
|
||||
if ($('#stbulk')) $('#stbulk').onchange = e => { const fs = [...e.target.files]; e.target.value = ''; if (fs.length) stBulk(fs, draw); };
|
||||
};
|
||||
draw();
|
||||
}
|
||||
@@ -246,3 +247,24 @@ async function stHomeExt(el) {
|
||||
return `<a class="tile" href="${esc(href)}" ${ext ? 'target="_blank" rel="noopener noreferrer"' : ''}><span class="ic" style="background:hsl(${stHue(i.title)} 55% 42%);font-weight:700;font-size:20px">${esc(i.icon || (i.title.match(/\p{L}|\p{N}/u) || ['?'])[0].toUpperCase())}</span><b>${esc(i.title)}</b><small>${i.kind === 'extension' ? 'Extension' : 'Site'}</small></a>`; };
|
||||
el.innerHTML = `<h2>My apps</h2><div class="grid">${mine.map(tileOf).join('')}</div>`;
|
||||
}
|
||||
|
||||
// Bulk add books: each file goes up on its own and the server reads the title, author and cover out of the book.
|
||||
async function stBulk(files, redraw) {
|
||||
let stop = false, done = 0; const added = [], dups = [], failed = [], noCover = [], noAuthor = [];
|
||||
const el = openSheet('Adding books', '', '<p class="sub" id="sbn"></p><div style="height:6px;border-radius:3px;background:var(--line);overflow:hidden"><div id="sbp" style="height:100%;width:0;background:var(--accent)"></div></div><p class="sub" id="sbl" style="margin-top:10px;word-break:break-word"></p><div id="sbr"></div><div class="bar"><button class="btn ghost" id="sbx" style="flex:1;justify-content:center">Stop</button></div>');
|
||||
const q = s => el.querySelector(s); q('#sbx').onclick = () => { if (done >= files.length || stop) closeSheet(); else { stop = true; q('#sbx').textContent = 'Stopping…'; } };
|
||||
for (const f of files) {
|
||||
if (stop) break;
|
||||
q('#sbn').textContent = `Book ${done + 1} of ${files.length}`; q('#sbl').textContent = f.name;
|
||||
try {
|
||||
const r = await stApi('/import?name=' + encodeURIComponent(f.name), { method: 'POST', body: f, headers: { 'content-type': 'application/octet-stream' } });
|
||||
if (r.duplicate) dups.push(f.name); else { added.push(r.item); if (r.noCover) noCover.push(r.item.title); if (r.noAuthor) noAuthor.push(r.item.title); }
|
||||
} catch (x) { failed.push(f.name + ': ' + x.message); }
|
||||
q('#sbp').style.width = (++done / files.length * 100) + '%';
|
||||
}
|
||||
const list = (t, a) => a.length ? `<details style="margin:6px 0"><summary>${t} (${a.length})</summary><div class="sub" style="max-height:25dvh;overflow:auto;word-break:break-word">${a.map(esc).join('<br>')}</div></details>` : '';
|
||||
q('#sbn').textContent = `${stop ? 'Stopped. ' : ''}Added ${added.length} of ${files.length} books`; q('#sbl').textContent = '';
|
||||
q('#sbr').innerHTML = list('Already in the store, skipped', dups) + list('No cover found', noCover) + list('No author found', noAuthor) + list('Could not be added', failed) + ((noCover.length || noAuthor.length) ? '<p class="sub">Open a book and tap Edit to fix its author or cover.</p>' : '');
|
||||
q('#sbx').textContent = 'Done'; done = files.length;
|
||||
try { await stLoad(); } catch {} redraw();
|
||||
}
|
||||
Reference in new issue
Block a user