From 4ae6f0ab42f2d1b1e797301cefbfa13e36298cee Mon Sep 17 00:00:00 2001 From: Admin Date: Mon, 2 Mar 2026 21:37:32 +0500 Subject: [PATCH] feat(ui): add book list page and book detail page with chapter index --- ui/src/routes/books/+page.server.ts | 17 +++ ui/src/routes/books/+page.svelte | 91 +++++++++++++ ui/src/routes/books/[slug]/+page.server.ts | 23 ++++ ui/src/routes/books/[slug]/+page.svelte | 142 +++++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 ui/src/routes/books/+page.server.ts create mode 100644 ui/src/routes/books/+page.svelte create mode 100644 ui/src/routes/books/[slug]/+page.server.ts create mode 100644 ui/src/routes/books/[slug]/+page.svelte diff --git a/ui/src/routes/books/+page.server.ts b/ui/src/routes/books/+page.server.ts new file mode 100644 index 0000000..615b208 --- /dev/null +++ b/ui/src/routes/books/+page.server.ts @@ -0,0 +1,17 @@ +import type { PageServerLoad } from './$types'; +import { listBooks, allProgress } from '$lib/server/pocketbase'; + +export const load: PageServerLoad = async ({ locals }) => { + const [books, progressList] = await Promise.all([ + listBooks(), + allProgress(locals.sessionId) + ]); + + // Build a quick lookup: slug → last chapter read + const progressMap: Record = {}; + for (const p of progressList) { + progressMap[p.slug] = p.chapter; + } + + return { books, progressMap }; +}; diff --git a/ui/src/routes/books/+page.svelte b/ui/src/routes/books/+page.svelte new file mode 100644 index 0000000..2138d13 --- /dev/null +++ b/ui/src/routes/books/+page.svelte @@ -0,0 +1,91 @@ + + + + Library — libnovel + + +
+

Library

+

{data.books.length} books

+
+ +{#if data.books.length === 0} +
+

No books scraped yet.

+

Use the scraper API to add books.

+
+{:else} + +{/if} diff --git a/ui/src/routes/books/[slug]/+page.server.ts b/ui/src/routes/books/[slug]/+page.server.ts new file mode 100644 index 0000000..e6b0e4e --- /dev/null +++ b/ui/src/routes/books/[slug]/+page.server.ts @@ -0,0 +1,23 @@ +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { getBook, listChapterIdx, getProgress } from '$lib/server/pocketbase'; + +export const load: PageServerLoad = async ({ params, locals }) => { + const { slug } = params; + + const [book, chapters, progress] = await Promise.all([ + getBook(slug), + listChapterIdx(slug), + getProgress(locals.sessionId, slug) + ]); + + if (!book) { + error(404, `Book "${slug}" not found`); + } + + return { + book, + chapters, + lastChapter: progress?.chapter ?? null + }; +}; diff --git a/ui/src/routes/books/[slug]/+page.svelte b/ui/src/routes/books/[slug]/+page.svelte new file mode 100644 index 0000000..26c36a1 --- /dev/null +++ b/ui/src/routes/books/[slug]/+page.svelte @@ -0,0 +1,142 @@ + + + + {data.book.title} — libnovel + + + +
+ {#if data.book.cover} + {data.book.title} + {/if} + +
+

{data.book.title}

+ + {#if data.book.author} +

{data.book.author}

+ {/if} + +
+ {#if data.book.status} + {data.book.status} + {/if} + {#each genres as genre} + {genre} + {/each} +
+ + {#if data.book.summary} +

{data.book.summary}

+ {/if} + +
+ {#if data.lastChapter} + + Continue ch.{data.lastChapter} + + {/if} + {#if data.chapters.length > 0} + + Start from ch.1 + + {/if} +
+
+
+ + +
+
+

+ Chapters + ({data.chapters.length}) +

+ + {#if totalPages > 1} +
+ + {page + 1} / {totalPages} + +
+ {/if} +
+ + {#if data.chapters.length === 0} +

No chapters available yet.

+ {:else} + + {/if} +