feat(koreader): add not-on-device tile, bulk folder picker, browse counts

Add a "Not on device" Browse tile listing recently added books not yet
downloaded here. On-device state is local to KOReader and has no
server-side filter, so the list is assembled by a bounded walk of the
newest books, capped by both a page budget and the server's ids limit.
It takes the slot of the Search tile, which duplicated the title bar
search button present on every non-detail page.

Add a folder picker to the bulk download settings, sharing one chooser
with the single-book dialog so both write the same download_dir.

Badge every Browse tile the server can count. Authors and series reuse
the cached totals the web sidebar already requests; libraries,
collections, smart scopes and in-progress get dedicated count queries
rather than the list methods behind the section endpoints, which
aggregate per-entity book counts a badge never reads. Badges are
right-aligned and abbreviated past 999 so the column stays fixed.

Replace the shared count path that paged and hydrated a book to read a
single integer, removing five queries per count on the dashboard, the
series read-through summary and the section endpoints.

Closes #869
This commit is contained in:
neon
2026-08-02 22:35:37 +00:00
parent 4be84710bd
commit 57e2616aec
15 changed files with 882 additions and 68 deletions
+14
View File
@@ -300,11 +300,25 @@ export interface KoreaderCatalogDashboardSection {
books: KoreaderCatalogBookListItem[];
}
// Totals behind the dashboard's Browse tiles. Optional so a plugin reading an
// older server simply renders the tiles without badges. There is deliberately
// no not-on-device total: which books are on a device is local KOReader state
// the server never sees.
export interface KoreaderCatalogBrowseCounts {
inProgress: number;
libraries: number;
authors: number;
series: number;
collections: number;
smartScopes: number;
}
export interface KoreaderCatalogDashboardResponse {
generatedAt: string;
username: string;
displayName: string;
totalBooks: number;
browseCounts?: KoreaderCatalogBrowseCounts;
sections: KoreaderCatalogEntry[];
continueReading: KoreaderCatalogBookListItem[];
// Legacy Discover row. Populated only for requests that name no section, so a