[FA-6] Volumes work probably?

This commit is contained in:
gamer147
2025-12-29 21:28:07 -05:00
parent bee805c441
commit d87bd81190
13 changed files with 286 additions and 63 deletions

View File

@@ -38,6 +38,12 @@
import { Badge } from '$lib/components/ui/badge';
import { Button } from '$lib/components/ui/button';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '$lib/components/ui/tabs';
import {
Accordion,
AccordionItem,
AccordionTrigger,
AccordionContent
} from '$lib/components/ui/accordion';
import {
Tooltip,
TooltipTrigger,
@@ -81,6 +87,7 @@
type GalleryImage = {
src: string;
alt: string;
volumeId?: number;
chapterId?: number;
chapterOrder?: number;
chapterName?: string;
@@ -114,11 +121,16 @@
: descriptionHtml
);
const sortedChapters = $derived(
[...(novel?.chapters ?? [])].sort((a, b) => a.order - b.order)
// Volume-aware chapter organization
const sortedVolumes = $derived(
[...(novel?.volumes ?? [])].sort((a, b) => a.order - b.order)
);
const chapterCount = $derived(novel?.chapters?.length ?? 0);
const isSingleVolume = $derived(sortedVolumes.length === 1);
const chapterCount = $derived(
sortedVolumes.reduce((sum, v) => sum + v.chapters.length, 0)
);
// Filter out system tags for display, check for NSFW
const displayTags = $derived(novel?.tags?.filter((tag) => tag.tagType !== TagType.System) ?? []);
@@ -139,18 +151,22 @@
images.push({ src: coverSrc, alt: `${novel.name} cover`, isCover: true });
}
// Add chapter images
for (const chapter of sortedChapters) {
for (const img of chapter.images ?? []) {
if (img.newPath) {
images.push({
src: img.newPath,
alt: `Image from ${chapter.name}`,
chapterId: chapter.id,
chapterOrder: chapter.order,
chapterName: chapter.name,
isCover: false
});
// Add chapter images (loop through volumes to preserve volumeId)
for (const volume of sortedVolumes) {
const volumeChapters = [...volume.chapters].sort((a, b) => a.order - b.order);
for (const chapter of volumeChapters) {
for (const img of chapter.images ?? []) {
if (img.newPath) {
images.push({
src: img.newPath,
alt: `Image from ${chapter.name}`,
volumeId: volume.id,
chapterId: chapter.id,
chapterOrder: chapter.order,
chapterName: chapter.name,
isCover: false
});
}
}
}
}
@@ -522,16 +538,18 @@
<CardContent class="pt-4">
<TabsContent value="chapters" class="mt-0">
{#if sortedChapters.length === 0}
{#if chapterCount === 0}
<p class="text-muted-foreground text-sm py-4 text-center">
No chapters available yet.
</p>
{:else}
{:else if isSingleVolume}
<!-- Single volume: flat chapter list -->
{@const singleVolumeChapters = [...(sortedVolumes[0]?.chapters ?? [])].sort((a, b) => a.order - b.order)}
<div class="max-h-96 overflow-y-auto -mx-2">
{#each sortedChapters as chapter (chapter.id)}
{#each singleVolumeChapters as chapter (chapter.id)}
{@const chapterDate = chapter.lastUpdatedTime ? new Date(chapter.lastUpdatedTime) : null}
<a
href="/novels/{novelId}/chapters/{chapter.order}"
href="/novels/{novelId}/volumes/{sortedVolumes[0]?.id}/chapters/{chapter.order}"
class="flex items-center justify-between px-3 py-2.5 hover:bg-muted/50 rounded-md transition-colors group"
>
<div class="flex items-center gap-3 min-w-0">
@@ -550,6 +568,50 @@
</a>
{/each}
</div>
{:else}
<!-- Multiple volumes: accordion display -->
<div class="max-h-96 overflow-y-auto -mx-2">
<Accordion type="single">
{#each sortedVolumes as volume (volume.id)}
{@const volumeChapters = [...volume.chapters].sort((a, b) => a.order - b.order)}
<AccordionItem value="volume-{volume.id}">
<AccordionTrigger class="px-3">
<div class="flex items-center gap-3">
<span class="font-medium">{volume.name}</span>
<span class="text-xs text-muted-foreground">
({volumeChapters.length} chapters)
</span>
</div>
</AccordionTrigger>
<AccordionContent>
<div class="space-y-0.5">
{#each volumeChapters as chapter (chapter.id)}
{@const chapterDate = chapter.lastUpdatedTime ? new Date(chapter.lastUpdatedTime) : null}
<a
href="/novels/{novelId}/volumes/{volume.id}/chapters/{chapter.order}"
class="flex items-center justify-between px-3 py-2.5 hover:bg-muted/50 rounded-md transition-colors group"
>
<div class="flex items-center gap-3 min-w-0">
<span class="text-muted-foreground text-sm font-medium shrink-0 w-14">
Ch. {chapter.order}
</span>
<span class="text-sm truncate group-hover:text-primary transition-colors">
{chapter.name}
</span>
</div>
{#if chapterDate}
<span class="text-xs text-muted-foreground/70 shrink-0 ml-2">
{formatRelativeTime(chapterDate)}
</span>
{/if}
</a>
{/each}
</div>
</AccordionContent>
</AccordionItem>
{/each}
</Accordion>
</div>
{/if}
</TabsContent>
@@ -645,9 +707,9 @@
/>
<!-- Chapter link (if not cover) -->
{#if !currentImage.isCover && currentImage.chapterOrder}
{#if !currentImage.isCover && currentImage.volumeId && currentImage.chapterOrder}
<a
href="/novels/{novelId}/chapters/{currentImage.chapterOrder}"
href="/novels/{novelId}/volumes/{currentImage.volumeId}/chapters/{currentImage.chapterOrder}"
class="text-white/80 hover:text-white text-sm inline-flex items-center gap-1 mt-3"
>
From: Ch. {currentImage.chapterOrder} - {currentImage.chapterName}