Chores, Family page, genre search, child accounts, wider Search and Notifications center
build-container / build (push) Successful in 4m6s
build-container / build (push) Successful in 4m6s
- Chores tab with repeats, points and a week/month board - Family page: calendar, chores, shopping, notes, chat and tasks on one start page - Genre search in the Store, Free Books and LibriVox - Child accounts: safe content, daily limit and bedtime, ask a parent - Search now covers notes, chat, contacts, chores, recipes and your books - Notifications center: bell with a per-person list of chat, reminders and announcements - Roadmap, README and tests updated Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf292e3bc7
commit
0383da87f5
38 files changed
+720
-79
No files matched your search
+7
-3
@@ -53,8 +53,8 @@ const shelf = (type, cat, page) => getJson(`${BASE()}/api/bookshelves/category/v
|
||||
const PER_API_PAGE = 10, API_PAGES_PER_PAGE = 3; // a page of the Store is three of Smashwords' pages of ten, since the filter removes some
|
||||
|
||||
/** The most downloaded free books, a page at a time. */
|
||||
async function browse(page) {
|
||||
const first = (page - 1) * API_PAGES_PER_PAGE + 1, pages = await Promise.all([0, 1, 2].map(i => shelf('mostdownloads', 1, first + i).catch(e => { if (i === 0) throw e; return { book_list: [], total_books: 0 }; })));
|
||||
async function browse(page, cat = 1) {
|
||||
const first = (page - 1) * API_PAGES_PER_PAGE + 1, pages = await Promise.all([0, 1, 2].map(i => shelf('mostdownloads', cat, first + i).catch(e => { if (i === 0) throw e; return { book_list: [], total_books: 0 }; })));
|
||||
const seen = new Set(), items = [];
|
||||
for (const p of pages) for (const e of p.book_list || []) { const b = e.book; if (b && !seen.has(b.id) && free(b) && hasEpub(b) && keep(b)) { seen.add(b.id); items.push(clean(item(b))); } }
|
||||
const total = pages[0].total_books || 0;
|
||||
@@ -73,7 +73,11 @@ async function buildPool() {
|
||||
return out;
|
||||
}
|
||||
const pool = () => { if (!poolP || Date.now() - poolAt > 6 * 3600e3) { poolAt = Date.now(); poolDone = false; poolP = buildPool().then(l => { poolDone = true; return l; }, e => { poolP = null; throw e; }); } return poolP; };
|
||||
async function search(q, page, wait = true) { // (wait: false is for a search across every source, which must not stall for the first-time build; it starts and the next search has it)
|
||||
async function search(q, page, wait = true, g = null) {
|
||||
if (g && g.sw) { // a genre: that category's most downloaded, and (with words) only those that mention them
|
||||
const words = q.toLowerCase().split(/\s+/).filter(Boolean), r = await browse(page, g.sw);
|
||||
return words.length ? { items: r.items.filter(b => words.every(x => (b.title + ' ' + b.author + ' ' + b.summary).toLowerCase().includes(x))), more: r.more } : r;
|
||||
} // (wait: false is for a search across every source, which must not stall for the first-time build; it starts and the next search has it)
|
||||
const words = q.toLowerCase().split(/\s+/).filter(Boolean);
|
||||
if (!words.length) return browse(page);
|
||||
if (!wait && !poolDone) { pool().catch(() => {}); return { items: [], more: false }; }
|
||||
|
||||
Reference in new issue
Block a user