diff --git a/ui/src/routes/books/[slug]/chapters/+page.server.ts b/ui/src/routes/books/[slug]/chapters/+page.server.ts new file mode 100644 index 0000000..9352791 --- /dev/null +++ b/ui/src/routes/books/[slug]/chapters/+page.server.ts @@ -0,0 +1,32 @@ +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { getBook, listChapterIdx, getProgress } from '$lib/server/pocketbase'; +import { log } from '$lib/server/logger'; + +export const load: PageServerLoad = async ({ params, locals }) => { + const { slug } = params; + + const book = await getBook(slug).catch((e) => { + log.error('chapters', 'getBook failed', { slug, err: String(e) }); + return null; + }); + + if (!book) error(404, `Book "${slug}" not found`); + + let chapters, progress; + try { + [chapters, progress] = await Promise.all([ + listChapterIdx(slug), + getProgress(locals.sessionId, slug, locals.user?.id) + ]); + } catch (e) { + log.error('chapters', 'failed to load chapters', { slug, err: String(e) }); + throw error(500, 'Failed to load chapters'); + } + + return { + book: { slug: book.slug, title: book.title, cover: book.cover ?? '', totalChapters: book.total_chapters }, + chapters, + lastChapter: progress?.chapter ?? null + }; +}; diff --git a/ui/src/routes/books/[slug]/chapters/+page.svelte b/ui/src/routes/books/[slug]/chapters/+page.svelte new file mode 100644 index 0000000..89a8ed9 --- /dev/null +++ b/ui/src/routes/books/[slug]/chapters/+page.svelte @@ -0,0 +1,203 @@ + + + + {data.book.title} — Chapters — libnovel + + + +
+ + + + + Back + + / +

{data.book.title}

+
+ + +
+ + + + + {#if searchQuery} + + {/if} +
+ + +{#if !searchQuery && totalGroups > 1} +
+ {#each Array(totalGroups) as _, i} + + {/each} +
+{/if} + + +{#if data.lastChapter && data.lastChapter > 0 && !searchQuery && activeGroup !== currentGroup} + +{/if} + + +{#if visibleChapters.length === 0} + {#if searchQuery} +

No chapters match "{searchQuery}"

+ {:else} +

No chapters available yet.

+ {/if} +{:else} + + {#if searchQuery} +

{visibleChapters.length} result{visibleChapters.length === 1 ? '' : 's'}

+ {/if} + +
+ {#each visibleChapters as chapter} + {@const isCurrent = data.lastChapter === chapter.number} + + + + {chapter.number} + + + + + {chapter.title || `Chapter ${chapter.number}`} + + + + {#if chapter.date_label} + + {/if} + + + {#if isCurrent} + reading + {/if} + + {/each} +
+ + + {#if !searchQuery && totalGroups > 1} +
+ {#each Array(totalGroups) as _, i} + + {/each} +
+ {/if} +{/if}