From 4a267d8fd8a5bbdc6d537ddac98459cd0d784585 Mon Sep 17 00:00:00 2001 From: Admin Date: Mon, 30 Mar 2026 14:16:22 +0500 Subject: [PATCH] feat: open catalogue + book pages to public (hybrid open model) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reading content is now publicly accessible without login: - /catalogue, /books/[slug], /books/[slug]/chapters, /books/[slug]/chapters/[n] are all public — no login redirect - /books (personal library), /profile, /admin remain protected Unauthenticated UX: - Chapter page: "Audio narration available — Sign in to listen" banner replaces the audio player for logged-out visitors - Book detail: bookmark icon links to /login instead of triggering the save action (both desktop and mobile CTAs) SEO: - robots.txt updated: Allow /books/ and /catalogue, Disallow /books (library) - sitemap.xml now includes /catalogue as primary crawl entry point i18n: added reader_signin_for_audio, reader_signin_audio_desc, book_detail_signin_to_save in all 5 languages Co-Authored-By: Claude Sonnet 4.6 --- ui/messages/en.json | 4 ++ ui/messages/fr.json | 4 ++ ui/messages/id.json | 4 ++ ui/messages/pt-BR.json | 4 ++ ui/messages/ru.json | 4 ++ ui/src/routes/+layout.server.ts | 10 +++-- ui/src/routes/books/[slug]/+page.svelte | 26 +++++++++++-- .../books/[slug]/chapters/[n]/+page.svelte | 32 ++++++++++++---- ui/src/routes/sitemap.xml/+server.ts | 37 +++++++++++++++++++ ui/static/robots.txt | 15 +++++++- 10 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 ui/src/routes/sitemap.xml/+server.ts diff --git a/ui/messages/en.json b/ui/messages/en.json index ed70450..a509a5f 100644 --- a/ui/messages/en.json +++ b/ui/messages/en.json @@ -48,6 +48,7 @@ "player_play": "Play", "player_pause": "Pause", "player_speed_label": "Playback speed {speed}x", + "player_seek_label": "Chapter progress", "player_change_speed": "Change playback speed", "player_auto_next_on": "Auto-next on", "player_auto_next_off": "Auto-next off", @@ -99,6 +100,7 @@ "catalogue_results_count": "{n} results", "book_detail_page_title": "{title} — libnovel", + "book_detail_signin_to_save": "Sign in to save", "book_detail_add_to_library": "Add to Library", "book_detail_remove_from_library": "Remove from Library", "book_detail_read_now": "Read Now", @@ -123,6 +125,8 @@ "reader_page_title": "{title} — Ch.{n} — libnovel", "reader_play_narration": "Play narration", "reader_generating_audio": "Generating audio…", + "reader_signin_for_audio": "Audio narration available", + "reader_signin_audio_desc": "Sign in to listen to this chapter narrated by AI.", "reader_audio_error": "Audio generation failed.", "reader_prev_chapter": "Previous chapter", "reader_next_chapter": "Next chapter", diff --git a/ui/messages/fr.json b/ui/messages/fr.json index ff297d5..52c0dc5 100644 --- a/ui/messages/fr.json +++ b/ui/messages/fr.json @@ -48,6 +48,7 @@ "player_play": "Lecture", "player_pause": "Pause", "player_speed_label": "Vitesse {speed}x", + "player_seek_label": "Progression du chapitre", "player_change_speed": "Changer la vitesse", "player_auto_next_on": "Suivant auto activé", "player_auto_next_off": "Suivant auto désactivé", @@ -99,6 +100,7 @@ "catalogue_results_count": "{n} résultats", "book_detail_page_title": "{title} — libnovel", + "book_detail_signin_to_save": "Connectez-vous pour sauvegarder", "book_detail_add_to_library": "Ajouter à la bibliothèque", "book_detail_remove_from_library": "Retirer de la bibliothèque", "book_detail_read_now": "Lire maintenant", @@ -123,6 +125,8 @@ "reader_page_title": "{title} — Ch.{n} — libnovel", "reader_play_narration": "Lire la narration", "reader_generating_audio": "Génération audio…", + "reader_signin_for_audio": "Narration audio disponible", + "reader_signin_audio_desc": "Connectez-vous pour écouter ce chapitre narré par l'IA.", "reader_audio_error": "Échec de la génération audio.", "reader_prev_chapter": "Chapitre précédent", "reader_next_chapter": "Chapitre suivant", diff --git a/ui/messages/id.json b/ui/messages/id.json index a2eb1cd..cce7dae 100644 --- a/ui/messages/id.json +++ b/ui/messages/id.json @@ -48,6 +48,7 @@ "player_play": "Putar", "player_pause": "Jeda", "player_speed_label": "Kecepatan {speed}x", + "player_seek_label": "Kemajuan bab", "player_change_speed": "Ubah kecepatan", "player_auto_next_on": "Auto-lanjut aktif", "player_auto_next_off": "Auto-lanjut nonaktif", @@ -99,6 +100,7 @@ "catalogue_results_count": "{n} hasil", "book_detail_page_title": "{title} — libnovel", + "book_detail_signin_to_save": "Masuk untuk menyimpan", "book_detail_add_to_library": "Tambah ke Perpustakaan", "book_detail_remove_from_library": "Hapus dari Perpustakaan", "book_detail_read_now": "Baca Sekarang", @@ -123,6 +125,8 @@ "reader_page_title": "{title} — Bab.{n} — libnovel", "reader_play_narration": "Putar narasi", "reader_generating_audio": "Membuat audio…", + "reader_signin_for_audio": "Narasi audio tersedia", + "reader_signin_audio_desc": "Masuk untuk mendengarkan bab ini yang dinarasikan oleh AI.", "reader_audio_error": "Pembuatan audio gagal.", "reader_prev_chapter": "Bab sebelumnya", "reader_next_chapter": "Bab berikutnya", diff --git a/ui/messages/pt-BR.json b/ui/messages/pt-BR.json index e049b66..e36b04d 100644 --- a/ui/messages/pt-BR.json +++ b/ui/messages/pt-BR.json @@ -48,6 +48,7 @@ "player_play": "Reproduzir", "player_pause": "Pausar", "player_speed_label": "Velocidade {speed}x", + "player_seek_label": "Progresso do capítulo", "player_change_speed": "Mudar velocidade", "player_auto_next_on": "Próximo automático ativado", "player_auto_next_off": "Próximo automático desativado", @@ -99,6 +100,7 @@ "catalogue_results_count": "{n} resultados", "book_detail_page_title": "{title} — libnovel", + "book_detail_signin_to_save": "Entre para salvar", "book_detail_add_to_library": "Adicionar à Biblioteca", "book_detail_remove_from_library": "Remover da Biblioteca", "book_detail_read_now": "Ler Agora", @@ -123,6 +125,8 @@ "reader_page_title": "{title} — Cap.{n} — libnovel", "reader_play_narration": "Reproduzir narração", "reader_generating_audio": "Gerando áudio…", + "reader_signin_for_audio": "Narração de áudio disponível", + "reader_signin_audio_desc": "Entre para ouvir este capítulo narrado por IA.", "reader_audio_error": "Falha na geração de áudio.", "reader_prev_chapter": "Capítulo anterior", "reader_next_chapter": "Próximo capítulo", diff --git a/ui/messages/ru.json b/ui/messages/ru.json index 983cb80..4992f56 100644 --- a/ui/messages/ru.json +++ b/ui/messages/ru.json @@ -48,6 +48,7 @@ "player_play": "Воспроизвести", "player_pause": "Пауза", "player_speed_label": "Скорость {speed}x", + "player_seek_label": "Прогресс главы", "player_change_speed": "Изменить скорость", "player_auto_next_on": "Автопереход вкл.", "player_auto_next_off": "Автопереход выкл.", @@ -99,6 +100,7 @@ "catalogue_results_count": "{n} результатов", "book_detail_page_title": "{title} — libnovel", + "book_detail_signin_to_save": "Войдите, чтобы сохранить", "book_detail_add_to_library": "В библиотеку", "book_detail_remove_from_library": "Удалить из библиотеки", "book_detail_read_now": "Читать", @@ -123,6 +125,8 @@ "reader_page_title": "{title} — Гл.{n} — libnovel", "reader_play_narration": "Воспроизвести озвучку", "reader_generating_audio": "Генерация аудио…", + "reader_signin_for_audio": "Доступна аудионарративация", + "reader_signin_audio_desc": "Войдите, чтобы слушать эту главу в озвучке ИИ.", "reader_audio_error": "Ошибка генерации аудио.", "reader_prev_chapter": "Предыдущая глава", "reader_next_chapter": "Следующая глава", diff --git a/ui/src/routes/+layout.server.ts b/ui/src/routes/+layout.server.ts index 7973002..df26747 100644 --- a/ui/src/routes/+layout.server.ts +++ b/ui/src/routes/+layout.server.ts @@ -4,11 +4,15 @@ import { getSettings } from '$lib/server/pocketbase'; import { log } from '$lib/server/logger'; // Routes that are accessible without being logged in -const PUBLIC_ROUTES = new Set(['/login', '/disclaimer', '/privacy', '/dmca', '/terms']); +const PUBLIC_ROUTES = new Set(['/login', '/disclaimer', '/privacy', '/dmca', '/terms', '/catalogue']); export const load: LayoutServerLoad = async ({ locals, url, cookies }) => { - // Allow /auth/* (OAuth initiation + callbacks) without login - const isPublic = PUBLIC_ROUTES.has(url.pathname) || url.pathname.startsWith('/auth/'); + // Allow public routes, /auth/*, and all book-browsing URLs (/books/[slug] and deeper) + // Note: /books (the personal library) is intentionally NOT public + const isPublic = + PUBLIC_ROUTES.has(url.pathname) || + url.pathname.startsWith('/auth/') || + url.pathname.startsWith('/books/'); if (!isPublic && !locals.user) { redirect(302, `/login`); } diff --git a/ui/src/routes/books/[slug]/+page.svelte b/ui/src/routes/books/[slug]/+page.svelte index 018a95f..39b6c5d 100644 --- a/ui/src/routes/books/[slug]/+page.svelte +++ b/ui/src/routes/books/[slug]/+page.svelte @@ -141,7 +141,7 @@

{m.book_detail_scraping_progress()}

- {#if data.taskId} + {#if data.taskId && data.user?.role === 'admin'}

task: {data.taskId}

{/if} @@ -243,7 +243,17 @@ {data.inLib ? m.book_detail_start_ch1() : m.book_detail_preview_ch1()} {/if} - {#if data.inLib} + {#if !data.isLoggedIn} + + + + + + {:else if data.inLib}