Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45f5c51da6 | ||
|
|
55df88c3e5 |
@@ -239,6 +239,11 @@ jobs:
|
||||
name: ui-build-injected
|
||||
path: ui/build
|
||||
|
||||
- name: Allow build/ into Docker context (override .dockerignore)
|
||||
run: |
|
||||
grep -v '^build$' ui/.dockerignore > ui/.dockerignore.tmp
|
||||
mv ui/.dockerignore.tmp ui/.dockerignore
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
|
||||
@@ -857,12 +857,6 @@
|
||||
aria-label={audioStore.chapters.length > 0 ? m.player_toggle_chapter_list() : undefined}
|
||||
title={audioStore.chapters.length > 0 ? m.player_chapter_list_label() : undefined}
|
||||
>
|
||||
{#if audioStore.chapterTitle}
|
||||
<p class="text-xs text-(--color-text) truncate leading-tight">{audioStore.chapterTitle}</p>
|
||||
{/if}
|
||||
{#if audioStore.bookTitle}
|
||||
<p class="text-xs text-(--color-muted) truncate leading-tight">{audioStore.bookTitle}</p>
|
||||
{/if}
|
||||
{#if audioStore.status === 'generating'}
|
||||
<p class="text-xs text-(--color-brand) leading-tight">
|
||||
{m.player_generating({ percent: String(Math.round(audioStore.progress)) })}
|
||||
|
||||
@@ -2,7 +2,11 @@ import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { backendFetch } from '$lib/server/scraper';
|
||||
|
||||
export const GET: RequestHandler = async ({ params, url }) => {
|
||||
export const GET: RequestHandler = async ({ params, url, locals }) => {
|
||||
if (!locals.isPro) {
|
||||
error(403, 'EPUB download requires a Pro subscription');
|
||||
}
|
||||
|
||||
const { slug } = params;
|
||||
const from = url.searchParams.get('from');
|
||||
const to = url.searchParams.get('to');
|
||||
|
||||
@@ -64,8 +64,9 @@ export const load: PageServerLoad = async ({ params, locals }) => {
|
||||
lastChapter: null,
|
||||
userRating: 0,
|
||||
ratingAvg: { avg: 0, count: 0 },
|
||||
isAdmin: locals.user?.role === 'admin',
|
||||
isLoggedIn: !!locals.user,
|
||||
isAdmin: locals.user?.role === 'admin',
|
||||
isPro: locals.isPro,
|
||||
isLoggedIn: !!locals.user,
|
||||
currentUserId: locals.user?.id ?? '',
|
||||
scraping: true,
|
||||
taskId: body.task_id
|
||||
|
||||
@@ -326,7 +326,7 @@
|
||||
let chapNamesPreview = $state<{ number: number; old_title: string; new_title: string; edited: string }[]>([]);
|
||||
let chapNamesApplying = $state(false);
|
||||
let chapNamesResult = $state<'applied' | 'error' | ''>('');
|
||||
let chapNamesPattern = $state('Chapter {n}: {scene}');
|
||||
let chapNamesPattern = $state('{scene}');
|
||||
let chapNamesBatchProgress = $state('');
|
||||
let chapNamesBatchWarnings = $state<string[]>([]);
|
||||
|
||||
@@ -342,7 +342,7 @@
|
||||
const res = await fetch('/api/admin/text-gen/chapter-names', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ slug, pattern: chapNamesPattern.trim() || 'Chapter {n}: {scene}' })
|
||||
body: JSON.stringify({ slug, pattern: chapNamesPattern.trim() || '{scene}' })
|
||||
});
|
||||
if (!res.ok) {
|
||||
chapNamesResult = 'error';
|
||||
@@ -835,13 +835,22 @@
|
||||
<p class="text-sm font-medium text-(--color-text)">Download</p>
|
||||
<p class="text-xs text-(--color-muted)">All {chapterList.length} chapters as EPUB</p>
|
||||
</div>
|
||||
<a
|
||||
href="/api/export/{book.slug}"
|
||||
download="{book.slug}.epub"
|
||||
class="px-3 py-1.5 rounded-lg bg-(--color-surface-2) border border-(--color-border) text-sm font-medium text-(--color-muted) hover:text-(--color-text) hover:border-zinc-500 transition-colors flex-shrink-0"
|
||||
>
|
||||
.epub
|
||||
</a>
|
||||
{#if data.isPro}
|
||||
<a
|
||||
href="/api/export/{book.slug}"
|
||||
download="{book.slug}.epub"
|
||||
class="px-3 py-1.5 rounded-lg bg-(--color-surface-2) border border-(--color-border) text-sm font-medium text-(--color-muted) hover:text-(--color-text) hover:border-zinc-500 transition-colors flex-shrink-0"
|
||||
>
|
||||
.epub
|
||||
</a>
|
||||
{:else}
|
||||
<a
|
||||
href="/profile"
|
||||
class="px-3 py-1.5 rounded-lg bg-(--color-brand)/15 border border-(--color-brand)/30 text-sm font-medium text-(--color-brand) hover:bg-(--color-brand)/25 transition-colors flex-shrink-0"
|
||||
>
|
||||
Pro
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1135,7 +1144,7 @@
|
||||
<input
|
||||
type="text"
|
||||
bind:value={chapNamesPattern}
|
||||
placeholder="Chapter {'{n}'}: {'{scene}'}"
|
||||
placeholder="{'{scene}'}"
|
||||
class="w-full px-2 py-1.5 rounded bg-(--color-surface-3) border border-(--color-border) text-(--color-text) text-xs focus:outline-none focus:border-(--color-brand)"
|
||||
/>
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
|
||||
@@ -471,18 +471,28 @@
|
||||
</button>
|
||||
{#if audioExpanded}
|
||||
<div class="border border-t-0 border-(--color-border) rounded-b-lg overflow-hidden">
|
||||
<AudioPlayer
|
||||
slug={data.book.slug}
|
||||
chapter={data.chapter.number}
|
||||
chapterTitle={data.chapter.title || m.reader_chapter_n({ n: String(data.chapter.number) })}
|
||||
bookTitle={data.book.title}
|
||||
cover={data.book.cover}
|
||||
nextChapter={data.next}
|
||||
chapters={data.chapters}
|
||||
voices={data.voices}
|
||||
playerStyle={layout.playerStyle}
|
||||
onProRequired={() => { audioProRequired = true; }}
|
||||
/>
|
||||
{#if audioStore.slug === data.book.slug && audioStore.chapter === data.chapter.number && audioStore.active}
|
||||
<!-- Mini-player is already playing this chapter — don't duplicate controls -->
|
||||
<div class="px-4 py-3 flex items-center gap-2 text-sm text-(--color-muted)">
|
||||
<svg class="w-4 h-4 text-(--color-brand) shrink-0" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 3v10.55A4 4 0 1014 17V7h4V3h-6z"/>
|
||||
</svg>
|
||||
<span>Controls are in the player bar below.</span>
|
||||
</div>
|
||||
{:else}
|
||||
<AudioPlayer
|
||||
slug={data.book.slug}
|
||||
chapter={data.chapter.number}
|
||||
chapterTitle={data.chapter.title || m.reader_chapter_n({ n: String(data.chapter.number) })}
|
||||
bookTitle={data.book.title}
|
||||
cover={data.book.cover}
|
||||
nextChapter={data.next}
|
||||
chapters={data.chapters}
|
||||
voices={data.voices}
|
||||
playerStyle={layout.playerStyle}
|
||||
onProRequired={() => { audioProRequired = true; }}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user