From 82f8a9a36b0af010c940404debde4fcd9865bd00 Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Tue, 31 Mar 2026 22:55:26 -0500 Subject: [PATCH] Fix: Forgot Auto-Bookmark Toggle & NSFW On GenreDrill --- src/components/pages/GenreDrillPage.svelte | 6 +- src/components/reader/Reader.svelte | 3 +- src/components/settings/Settings.svelte | 81 +++++++++++++++++----- 3 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/components/pages/GenreDrillPage.svelte b/src/components/pages/GenreDrillPage.svelte index bb10caa..10b1304 100644 --- a/src/components/pages/GenreDrillPage.svelte +++ b/src/components/pages/GenreDrillPage.svelte @@ -4,7 +4,7 @@ import { gql, thumbUrl } from "../../lib/client"; import { GET_ALL_MANGA, GET_LIBRARY, GET_SOURCES, FETCH_SOURCE_MANGA, UPDATE_MANGA, GET_CATEGORIES, CREATE_CATEGORY, UPDATE_MANGA_CATEGORIES } from "../../lib/queries"; import { cache, CACHE_KEYS, getPageSet } from "../../lib/cache"; - import { dedupeSources, dedupeMangaById } from "../../lib/util"; + import { dedupeSources, dedupeMangaById, shouldHideNsfw } from "../../lib/util"; import { store, setGenreFilter, setPreviewManga, setNavPage } from "../../store/state.svelte"; import type { Manga, Source, Category } from "../../lib/types"; import ContextMenu, { type MenuEntry } from "../shared/ContextMenu.svelte"; @@ -47,9 +47,9 @@ let abortCtrl: AbortController | null = null; const filtered = $derived.by(() => { - const libMatches = libraryManga.filter((m) => matchesAllTags(m, tags)); + const libMatches = libraryManga.filter((m) => matchesAllTags(m, tags) && !shouldHideNsfw(m, store.settings)); const libIds = new Set(libMatches.map((m) => m.id)); - return dedupeMangaById([...libMatches, ...sourceManga.filter((m) => !libIds.has(m.id))]); + return dedupeMangaById([...libMatches, ...sourceManga.filter((m) => !libIds.has(m.id) && !shouldHideNsfw(m, store.settings))]); }); const visibleItems = $derived(filtered.slice(0, visibleCount)); const hasMoreVisible = $derived(visibleCount < filtered.length); diff --git a/src/components/reader/Reader.svelte b/src/components/reader/Reader.svelte index d847c3a..155ac57 100644 --- a/src/components/reader/Reader.svelte +++ b/src/components/reader/Reader.svelte @@ -430,6 +430,7 @@ $effect(() => { const ch = displayChapter ?? store.activeChapter; + const autoBookmark = store.settings.autoBookmark ?? true; if (ch && lastPage && store.activeManga) { const chapterId = ch.id; const chapterName = ch.name; @@ -443,7 +444,7 @@ if (!hasNavigated) return; if (style === "longstrip" && visibleChapterId && chapterId !== visibleChapterId) return; addHistory({ mangaId, mangaTitle, thumbnailUrl: thumb, chapterId, chapterName, readAt: Date.now() }); - if (store.settings.autoBookmark ?? true) { + if (autoBookmark) { addBookmark({ mangaId, mangaTitle, thumbnailUrl: thumb, chapterId, chapterName, pageNumber: pageNum }); } if (style !== "longstrip" && store.settings.autoMarkRead && atLast) markChapterRead(chapterId); diff --git a/src/components/settings/Settings.svelte b/src/components/settings/Settings.svelte index bc59e0b..7851022 100644 --- a/src/components/settings/Settings.svelte +++ b/src/components/settings/Settings.svelte @@ -77,6 +77,19 @@ let clearing = $state(false); let cleared = $state(false); + // ── External server detection ───────────────────────────────────────────────── + // A server is "external" if its URL doesn't point to localhost — in that case we + // cannot invoke Tauri commands against its filesystem (path validation, disk usage, + // migration). We can still read the server's paths via GraphQL, but we must never + // overwrite the server's download directory config without the user explicitly asking. + const isExternalServer = $derived.by(() => { + const url = (store.settings.serverUrl ?? "http://localhost:4567").toLowerCase().trim(); + try { + const host = new URL(url).hostname; + return host !== "localhost" && host !== "127.0.0.1" && host !== "::1"; + } catch { return false; } + }); + // ── Download path editing ──────────────────────────────────────────────────── let downloadsPathInput = $state(store.settings.serverDownloadsPath ?? ""); let localSourcePathInput = $state(store.settings.serverLocalSourcePath ?? ""); @@ -85,9 +98,17 @@ let pathsFieldError: { dl?: string; loc?: string } = $state({}); let pathsSaved = $state(false); - // The actual resolved default path from Rust — shown as placeholder + scanned when dl path is empty + // The actual resolved default path from Rust — shown as placeholder + scanned when dl path is empty. + // Only meaningful for local servers — Tauri can't stat a remote filesystem. + // Re-fetches reactively when the server URL changes (e.g. user switches to local). let defaultDownloadsPath = $state(""); - invoke("get_default_downloads_path").then(p => { defaultDownloadsPath = p; }); + $effect(() => { + if (!isExternalServer) { + invoke("get_default_downloads_path").then(p => { defaultDownloadsPath = p; }); + } else { + defaultDownloadsPath = ""; + } + }); // The last confirmed server paths — used to detect a change requiring migration let confirmedDownloadsPath = $state(store.settings.serverDownloadsPath ?? ""); @@ -110,6 +131,7 @@ async function fetchStorage() { storageLoading = true; storageError = null; try { + // Always pull the current paths from the server via GQL — works for local and external. const pathData = await gql<{ settings: { downloadsPath: string; localSourcePath: string } }>(GET_DOWNLOADS_PATH); const dl = pathData.settings.downloadsPath ?? ""; const loc = pathData.settings.localSourcePath ?? ""; @@ -120,6 +142,12 @@ confirmedLocalSourcePath = loc; updateSettings({ serverDownloadsPath: dl, serverLocalSourcePath: loc }); + // Disk usage scanning uses Tauri invoke — only possible when the server is local. + // For external servers we display the paths pulled above but skip the filesystem scan. + if (isExternalServer) { + multiStorageInfos = []; storageInfo = null; return; + } + // When dl is empty the server uses the default path — scan that instead const effectiveDl = dl || defaultDownloadsPath; @@ -152,9 +180,11 @@ } } - /** Validate a path exists on disk. Returns error string or null. */ + /** Validate a path exists on disk. Returns error string or null. + * Only runs for local servers — we can't stat a remote filesystem via Tauri. */ async function validatePath(path: string): Promise { if (!path.trim()) return null; // empty = use default, always valid + if (isExternalServer) return null; // can't check remote paths locally try { const exists = await invoke("check_path_exists", { path: path.trim() }); return exists ? null : "Directory does not exist"; @@ -163,8 +193,9 @@ } } - /** Create a directory on disk via Tauri. */ + /** Create a directory on disk via Tauri. Only valid for local servers. */ async function createDirectory(path: string): Promise { + if (isExternalServer) throw new Error("Cannot create directories on an external server"); await invoke("create_directory", { path }); } @@ -173,7 +204,8 @@ const loc = localSourcePathInput.trim(); pathsError = null; pathsFieldError = {}; - // Validate paths exist before touching the server (empty = use default = always valid) + // Validate paths exist before touching the server (empty = use default = always valid). + // Skipped for external servers — we can't stat their filesystem. const [dlErr, locErr] = await Promise.all([validatePath(dl), validatePath(loc)]); if (dlErr || locErr) { pathsFieldError = { ...(dlErr ? { dl: dlErr } : {}), ...(locErr ? { loc: locErr } : {}) }; @@ -188,12 +220,14 @@ updateSettings({ serverDownloadsPath: dl, serverLocalSourcePath: loc }); - // If downloads path changed and old path had content, offer migration - const oldDl = confirmedDownloadsPath || defaultDownloadsPath; - const newDl = dl || defaultDownloadsPath; - if (newDl && oldDl && newDl !== oldDl) { - const hadContent = await invoke("check_path_exists", { path: oldDl }); - if (hadContent) { migrateFrom = oldDl; migrateTo = newDl; } + // Migration requires local filesystem access — skip for external servers. + if (!isExternalServer) { + const oldDl = confirmedDownloadsPath || defaultDownloadsPath; + const newDl = dl || defaultDownloadsPath; + if (newDl && oldDl && newDl !== oldDl) { + const hadContent = await invoke("check_path_exists", { path: oldDl }); + if (hadContent) { migrateFrom = oldDl; migrateTo = newDl; } + } } confirmedDownloadsPath = dl; @@ -1271,7 +1305,7 @@
- {#if migrateFrom} + {#if migrateFrom && !isExternalServer}
Manga found at previous path — move to new location? @@ -1298,11 +1332,13 @@
-

Disk Usage

+

Disk Usage

{#if storageLoading}

Reading filesystem…

{:else if storageError}

{storageError}

+ {:else if isExternalServer} +

Disk usage is unavailable for external servers — filesystem access requires a local connection.

{:else if multiStorageInfos.length > 0} {#each multiStorageInfos as info} {@const limitGb = store.settings.storageLimitGb ?? null} @@ -1332,12 +1368,17 @@

Downloads Path

+ {#if isExternalServer} +

+ Connected to an external server. The path below is read from the server — changes here will update the server's config directly. Make sure the path is valid on the server's filesystem. +

+ {/if}
e.key === "Enter" && savePaths()} oninput={() => { pathsFieldError = { ...pathsFieldError, dl: undefined }; }} @@ -1345,10 +1386,12 @@
{#if pathsFieldError.dl} {pathsFieldError.dl} - + {#if !isExternalServer} + + {/if} {/if} {#if pathsError}{pathsError}{/if}