chore: clear the mechanical lint and type findings

Dead imports and locals removed, each blocks keyed, `any` narrowed to unknown.
Two context setters kept their calls and lost only the unused binding; the
settings redirect no longer awaits a parent whose data it discards. A leading
underscore now marks a binding that only holds a position.
This commit is contained in:
2026-08-17 17:55:21 -04:00
parent 428168c07a
commit 3a29294f96
36 changed files with 76 additions and 91 deletions
+12 -1
View File
@@ -28,7 +28,18 @@ export default defineConfig(
rules: { rules: {
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
'no-undef': 'off' 'no-undef': 'off',
// A leading underscore marks a binding that exists to hold a position — a callback
// parameter the signature requires, or the discarded half of a destructure.
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_'
}
]
} }
}, },
{ {
+1 -1
View File
@@ -4,7 +4,7 @@ import { BACKEND_API_URL } from '$lib/server/config';
import { invalid, redirect } from '@sveltejs/kit'; import { invalid, redirect } from '@sveltejs/kit';
export const login = form(loginSchema, async (data, issue) => { export const login = form(loginSchema, async (data, issue) => {
const { cookies, locals } = getRequestEvent(); const { cookies } = getRequestEvent();
// Create URL-encoded form data // Create URL-encoded form data
const formData = new URLSearchParams(); const formData = new URLSearchParams();
+1 -1
View File
@@ -25,6 +25,6 @@ export const createLibrary = form(libraryCreateSchema, async (data) => {
return await response.json(); return await response.json();
}); });
export const deleteLibrary = query('unchecked', async (data) => { export const deleteLibrary = query('unchecked', async (_data) => {
throw new Error('Not implemented'); throw new Error('Not implemented');
}); });
@@ -3,8 +3,6 @@
import { Checkbox } from '$lib/components/ui/checkbox/index.js'; import { Checkbox } from '$lib/components/ui/checkbox/index.js';
import { Button } from '$lib/components/ui/button/index.js'; import { Button } from '$lib/components/ui/button/index.js';
import * as Dialog from '$lib/components/ui/dialog/index.js'; import * as Dialog from '$lib/components/ui/dialog/index.js';
import { getBookSelectionState } from '$lib/state/bookSelection.svelte';
import { getBookOperationsState } from '$lib/state/bookOperations.svelte';
let { let {
open = $bindable(), open = $bindable(),
@@ -16,9 +14,6 @@
deleteFn: (deleteFiles: boolean) => void | Promise<void>; deleteFn: (deleteFiles: boolean) => void | Promise<void>;
} = $props(); } = $props();
const selectedState = getBookSelectionState();
const bookOps = getBookOperationsState();
let deleteFiles = $state(false); let deleteFiles = $state(false);
</script> </script>
@@ -39,7 +39,7 @@
<Field.Field> <Field.Field>
<Field.Label for="name">Device name</Field.Label> <Field.Label for="name">Device name</Field.Label>
<Input {...createDevice.fields.name.as('text')} placeholder="e.g. Kindle Paperwhite" /> <Input {...createDevice.fields.name.as('text')} placeholder="e.g. Kindle Paperwhite" />
{#each createDevice.fields.name.issues() ?? [] as issue} {#each createDevice.fields.name.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -128,7 +128,7 @@
<Field.Field class="col-span-full"> <Field.Field class="col-span-full">
<Field.Label for="title">Title</Field.Label> <Field.Label for="title">Title</Field.Label>
<Input {...updateBookMetadata.fields.title.as('text')} /> <Input {...updateBookMetadata.fields.title.as('text')} />
{#each updateBookMetadata.fields.title.issues() ?? [] as issue} {#each updateBookMetadata.fields.title.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -136,7 +136,7 @@
<Field.Field> <Field.Field>
<Field.Label for="subtitle">Subtitle</Field.Label> <Field.Label for="subtitle">Subtitle</Field.Label>
<Input {...updateBookMetadata.fields.subtitle.as('text')} /> <Input {...updateBookMetadata.fields.subtitle.as('text')} />
{#each updateBookMetadata.fields.subtitle.issues() ?? [] as issue} {#each updateBookMetadata.fields.subtitle.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -144,7 +144,7 @@
<Field.Field> <Field.Field>
<Field.Label for="edition">Edition</Field.Label> <Field.Label for="edition">Edition</Field.Label>
<Input {...updateBookMetadata.fields.edition.as('number')} /> <Input {...updateBookMetadata.fields.edition.as('number')} />
{#each updateBookMetadata.fields.edition.issues() ?? [] as issue} {#each updateBookMetadata.fields.edition.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -152,7 +152,7 @@
<Field.Field> <Field.Field>
<Field.Label for="series">Series</Field.Label> <Field.Label for="series">Series</Field.Label>
<Input {...updateBookMetadata.fields.series.as('text')} /> <Input {...updateBookMetadata.fields.series.as('text')} />
{#each updateBookMetadata.fields.series.issues() ?? [] as issue} {#each updateBookMetadata.fields.series.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -161,7 +161,7 @@
<Field.Field> <Field.Field>
<Field.Label for="series_position">No.</Field.Label> <Field.Label for="series_position">No.</Field.Label>
<Input {...updateBookMetadata.fields.series_position.as('text')} /> <Input {...updateBookMetadata.fields.series_position.as('text')} />
{#each updateBookMetadata.fields.series_position.issues() ?? [] as issue} {#each updateBookMetadata.fields.series_position.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -169,7 +169,7 @@
<Field.Field> <Field.Field>
<Field.Label for="language">Language</Field.Label> <Field.Label for="language">Language</Field.Label>
<Input {...updateBookMetadata.fields.language.as('text')} /> <Input {...updateBookMetadata.fields.language.as('text')} />
{#each updateBookMetadata.fields.language.issues() ?? [] as issue} {#each updateBookMetadata.fields.language.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -185,10 +185,10 @@
placeholder="Add an author" placeholder="Add an author"
class="min-h-10 p-2 text-sm" class="min-h-10 p-2 text-sm"
/> />
{#each authors as author} {#each authors as author, i (i)}
<input class="hidden" {...updateBookMetadata.fields.authors.as('checkbox', author)} /> <input class="hidden" {...updateBookMetadata.fields.authors.as('checkbox', author)} />
{/each} {/each}
{#each updateBookMetadata.fields.authors.issues() ?? [] as issue} {#each updateBookMetadata.fields.authors.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -201,10 +201,10 @@
placeholder="Add a tag" placeholder="Add a tag"
class="min-h-10 p-2 text-sm" class="min-h-10 p-2 text-sm"
/> />
{#each tags as tag} {#each tags as tag, i (i)}
<input class="hidden" {...updateBookMetadata.fields.tags.as('checkbox', tag)} /> <input class="hidden" {...updateBookMetadata.fields.tags.as('checkbox', tag)} />
{/each} {/each}
{#each updateBookMetadata.fields.tags.issues() ?? [] as issue} {#each updateBookMetadata.fields.tags.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -214,7 +214,7 @@
<Field.Field> <Field.Field>
<Field.Label for="publisher">Publisher</Field.Label> <Field.Label for="publisher">Publisher</Field.Label>
<Input {...updateBookMetadata.fields.publisher.as('text')} /> <Input {...updateBookMetadata.fields.publisher.as('text')} />
{#each updateBookMetadata.fields.publisher.issues() ?? [] as issue} {#each updateBookMetadata.fields.publisher.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -223,7 +223,7 @@
<Field.Field> <Field.Field>
<Field.Label for="published_date">Published</Field.Label> <Field.Label for="published_date">Published</Field.Label>
<Input {...updateBookMetadata.fields.published_date.as('date')} /> <Input {...updateBookMetadata.fields.published_date.as('date')} />
{#each updateBookMetadata.fields.published_date.issues() ?? [] as issue} {#each updateBookMetadata.fields.published_date.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -231,7 +231,7 @@
<Field.Field> <Field.Field>
<Field.Label for="pages">Pages</Field.Label> <Field.Label for="pages">Pages</Field.Label>
<Input {...updateBookMetadata.fields.pages.as('number')} /> <Input {...updateBookMetadata.fields.pages.as('number')} />
{#each updateBookMetadata.fields.pages.issues() ?? [] as issue} {#each updateBookMetadata.fields.pages.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -240,7 +240,7 @@
<Field.Field class="col-span-full"> <Field.Field class="col-span-full">
<Field.Label for="identifiers">Identifiers</Field.Label> <Field.Label for="identifiers">Identifiers</Field.Label>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{#each identifierKeys as _, idx} {#each identifierKeys as _, idx (idx)}
<div class="grid grid-cols-[1fr_1.6fr_auto] gap-2"> <div class="grid grid-cols-[1fr_1.6fr_auto] gap-2">
<Input bind:value={identifierKeys[idx]} placeholder="ISBN, DOI…" /> <Input bind:value={identifierKeys[idx]} placeholder="ISBN, DOI…" />
<Input bind:value={identifierValues[idx]} placeholder="Value" /> <Input bind:value={identifierValues[idx]} placeholder="Value" />
@@ -271,7 +271,7 @@
<Field.Field class="col-span-full"> <Field.Field class="col-span-full">
<Field.Label for="description" class="sr-only">Description</Field.Label> <Field.Label for="description" class="sr-only">Description</Field.Label>
<Textarea {...updateBookMetadata.fields.description.as('text')} rows={6} /> <Textarea {...updateBookMetadata.fields.description.as('text')} rows={6} />
{#each updateBookMetadata.fields.description.issues() ?? [] as issue} {#each updateBookMetadata.fields.description.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -56,7 +56,7 @@
<Field.Field> <Field.Field>
<Field.Label for="name">Library name</Field.Label> <Field.Label for="name">Library name</Field.Label>
<Input {...createLibrary.fields.name.as('text')} /> <Input {...createLibrary.fields.name.as('text')} />
{#each createLibrary.fields.name.issues() ?? [] as issue} {#each createLibrary.fields.name.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -73,7 +73,7 @@
<Field.Field> <Field.Field>
<Field.Label for="description">Description</Field.Label> <Field.Label for="description">Description</Field.Label>
<Textarea {...createLibrary.fields.description.as('text')} /> <Textarea {...createLibrary.fields.description.as('text')} />
{#each createLibrary.fields.description.issues() ?? [] as issue} {#each createLibrary.fields.description.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -82,7 +82,7 @@
<Field.Field> <Field.Field>
<Field.Label for="root_path">Root Path</Field.Label> <Field.Label for="root_path">Root Path</Field.Label>
<Input {...createLibrary.fields.root_path.as('text')} /> <Input {...createLibrary.fields.root_path.as('text')} />
{#each createLibrary.fields.root_path.issues() ?? [] as issue} {#each createLibrary.fields.root_path.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
<Field.Description class="text-xs"> <Field.Description class="text-xs">
@@ -94,7 +94,7 @@
<Field.Field> <Field.Field>
<Field.Label for="path_template">Path Template</Field.Label> <Field.Label for="path_template">Path Template</Field.Label>
<Input {...createLibrary.fields.path_template.as('text')} /> <Input {...createLibrary.fields.path_template.as('text')} />
{#each createLibrary.fields.path_template.issues() ?? [] as issue} {#each createLibrary.fields.path_template.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
<Field.Description class="text-xs"> <Field.Description class="text-xs">
@@ -21,7 +21,7 @@
<Field.Field> <Field.Field>
<Field.Label for="email">Email</Field.Label> <Field.Label for="email">Email</Field.Label>
<Input {...login.fields.email.as('email')} /> <Input {...login.fields.email.as('email')} />
{#each login.fields.email.issues() ?? [] as issue} {#each login.fields.email.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -30,7 +30,7 @@
<Field.Field> <Field.Field>
<Field.Label for="password">Password</Field.Label> <Field.Label for="password">Password</Field.Label>
<Input {...login.fields.password.as('password')} /> <Input {...login.fields.password.as('password')} />
{#each login.fields.password.issues() ?? [] as issue} {#each login.fields.password.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -47,7 +47,7 @@
<Field.Field> <Field.Field>
<Field.Label for="email">Email</Field.Label> <Field.Label for="email">Email</Field.Label>
<Input {...signup.fields.email.as('email')} /> <Input {...signup.fields.email.as('email')} />
{#each signup.fields.email.issues() ?? [] as issue} {#each signup.fields.email.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -56,7 +56,7 @@
<Field.Field> <Field.Field>
<Field.Label for="password">Password</Field.Label> <Field.Label for="password">Password</Field.Label>
<Input {...signup.fields.password.as('password')} /> <Input {...signup.fields.password.as('password')} />
{#each signup.fields.password.issues() ?? [] as issue} {#each signup.fields.password.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -65,7 +65,7 @@
<Field.Field> <Field.Field>
<Field.Label for="confirmPassword">Confirm Password</Field.Label> <Field.Label for="confirmPassword">Confirm Password</Field.Label>
<Input {...signup.fields.confirmPassword.as('password')} /> <Input {...signup.fields.confirmPassword.as('password')} />
{#each signup.fields.confirmPassword.issues() ?? [] as issue} {#each signup.fields.confirmPassword.issues() ?? [] as issue, i (i)}
<Field.Error>{issue.message}</Field.Error> <Field.Error>{issue.message}</Field.Error>
{/each} {/each}
</Field.Field> </Field.Field>
@@ -3,7 +3,7 @@
import * as Sidebar from '$lib/components/ui/sidebar/index.js'; import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import { useSidebar } from '$lib/components/ui/sidebar/index.js'; import { useSidebar } from '$lib/components/ui/sidebar/index.js';
import { Badge } from '$lib/components/ui/badge/index.js'; import { Badge } from '$lib/components/ui/badge/index.js';
import { getLibraryState, LibraryState } from '$lib/state/library.svelte'; import { getLibraryState } from '$lib/state/library.svelte';
import { LIBRARY_ICONS } from '$lib/components/ui/icon-picker/index.js'; import { LIBRARY_ICONS } from '$lib/components/ui/icon-picker/index.js';
import ChevronsUpDownIcon from '@lucide/svelte/icons/chevrons-up-down'; import ChevronsUpDownIcon from '@lucide/svelte/icons/chevrons-up-down';
import PlusIcon from '@lucide/svelte/icons/plus'; import PlusIcon from '@lucide/svelte/icons/plus';
@@ -53,9 +53,9 @@
} }
} }
function handleClick(e: any) { function handleClick(e: Event & { currentTarget: HTMLElement }) {
open = true; open = true;
e.target.blur(); e.currentTarget.blur();
} }
</script> </script>
@@ -93,7 +93,7 @@
</div> </div>
{:else} {:else}
<Command.List class="max-h-[600px]"> <Command.List class="max-h-[600px]">
{#if searchResult?.items.length > 0} {#if (searchResult?.items?.length ?? 0) > 0}
<Command.Group heading="Books"> <Command.Group heading="Books">
{#each searchResult?.items as book (book.id)} {#each searchResult?.items as book (book.id)}
<Command.Item <Command.Item
@@ -121,7 +121,7 @@
{#if book.authors.length > 0} {#if book.authors.length > 0}
<span class="line-clamp-1 w-full text-sm text-muted-foreground"> <span class="line-clamp-1 w-full text-sm text-muted-foreground">
by by
{#each book.authors as author} {#each book.authors as author (author.id)}
<a <a
href="{resolve('/(root)/(library)/library/[libraryId]/view', { href="{resolve('/(root)/(library)/library/[libraryId]/view', {
libraryId: String(libraryState.activeLibrary!.id) libraryId: String(libraryState.activeLibrary!.id)
@@ -132,7 +132,7 @@
</span> </span>
{/if} {/if}
<div class="mt-1 ml-[-1.5] flex"> <div class="mt-1 ml-[-1.5] flex">
{#each book.tags as tag} {#each book.tags as tag (tag.id)}
<Badge class="scale-75">{tag.name}</Badge> <Badge class="scale-75">{tag.name}</Badge>
{/each} {/each}
</div> </div>
@@ -4,7 +4,6 @@
import type { Book } from '$lib/schema'; import type { Book } from '$lib/schema';
import BookThumbnail from './book-thumbnail.svelte'; import BookThumbnail from './book-thumbnail.svelte';
import { goto } from '$app/navigation';
import { getBookSelectionState } from '$lib/state/bookSelection.svelte'; import { getBookSelectionState } from '$lib/state/bookSelection.svelte';
import { getBookOperationsState } from '$lib/state/bookOperations.svelte'; import { getBookOperationsState } from '$lib/state/bookOperations.svelte';
import { getLibraryState } from '$lib/state/library.svelte'; import { getLibraryState } from '$lib/state/library.svelte';
@@ -1,8 +1,10 @@
<script> <script>
let { src, fallback = '/images/default_cover.jpg', class: className = '' } = $props(); let { src, fallback = '/images/default_cover.jpg', class: className = '' } = $props();
async function handleError(e) { /** @param {Event} e */
e.target.src = fallback; function handleError(e) {
const img = /** @type {HTMLImageElement} */ (e.currentTarget);
img.src = fallback;
} }
</script> </script>
@@ -2,7 +2,6 @@
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import * as Table from '$lib/components/ui/table/index'; import * as Table from '$lib/components/ui/table/index';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index'; import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index';
import * as Tooltip from '$lib/components/ui/tooltip/index';
import { Checkbox } from '$lib/components/ui/checkbox/index'; import { Checkbox } from '$lib/components/ui/checkbox/index';
import { Badge } from '$lib/components/ui/badge/index'; import { Badge } from '$lib/components/ui/badge/index';
import { buttonVariants } from '$lib/components/ui/button/index.js'; import { buttonVariants } from '$lib/components/ui/button/index.js';
@@ -2,12 +2,11 @@
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import BookImage from './book-image.svelte'; import BookImage from './book-image.svelte';
import GeneratedCover from './generated-cover.svelte'; import GeneratedCover from './generated-cover.svelte';
import { Progress } from '$lib/components/ui/progress/index';
import { getLibraryState } from '$lib/state/library.svelte'; import { getLibraryState } from '$lib/state/library.svelte';
import { getBookSelectionState } from '$lib/state/bookSelection.svelte'; import { getBookSelectionState } from '$lib/state/bookSelection.svelte';
import type { Book } from '$lib/schema'; import type { Book } from '$lib/schema';
let { book, class: className = '', ...rest }: { book: Book; class?: string } = $props(); let { book, class: className = '' }: { book: Book; class?: string } = $props();
const selectionState = getBookSelectionState(); const selectionState = getBookSelectionState();
const libraryState = getLibraryState(); const libraryState = getLibraryState();
@@ -78,7 +77,7 @@
<!-- Authors list --> <!-- Authors list -->
<p class="line-clamp-1 w-full text-xs text-muted-foreground"> <p class="line-clamp-1 w-full text-xs text-muted-foreground">
{#each book.authors as author} {#each book.authors as author (author.id)}
<a <a
href="{resolve('/(root)/(library)/library/[libraryId]/view', { href="{resolve('/(root)/(library)/library/[libraryId]/view', {
libraryId: String(libraryState.activeLibrary?.id ?? '') libraryId: String(libraryState.activeLibrary?.id ?? '')
@@ -30,7 +30,7 @@
<Collapsible.Content class="h-full max-h-128 overflow-y-auto"> <Collapsible.Content class="h-full max-h-128 overflow-y-auto">
<Sidebar.GroupContent> <Sidebar.GroupContent>
<Sidebar.Menu> <Sidebar.Menu>
{#each filter.items as item, index (item.id)} {#each filter.items as item (item.id)}
<Sidebar.MenuItem <Sidebar.MenuItem
onclick={() => bookCollection.toggleFilter(filter.value, item.id.toString())} onclick={() => bookCollection.toggleFilter(filter.value, item.id.toString())}
> >
@@ -25,7 +25,7 @@
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Content class="w-40"> <DropdownMenu.Content class="w-40">
<DropdownMenu.Group> <DropdownMenu.Group>
{#each bookCollection.sortOptions as sortProp} {#each bookCollection.sortOptions as sortProp (sortProp.value)}
<DropdownMenu.Item <DropdownMenu.Item
onSelect={() => bookCollection.updateSort(sortProp.value)} onSelect={() => bookCollection.updateSort(sortProp.value)}
class={bookCollection.orderBy === sortProp.value ? 'bg-muted' : ''} class={bookCollection.orderBy === sortProp.value ? 'bg-muted' : ''}
-2
View File
@@ -4,8 +4,6 @@ const emailSchema = z
.email('Please enter a valid email address') .email('Please enter a valid email address')
.max(255, 'Email address cannot exceed 255 characters'); .max(255, 'Email address cannot exceed 255 characters');
const usernameSchema = z.string().min(1, 'Email is required');
const basePasswordSchema = z.string().min(1, 'Password is required'); const basePasswordSchema = z.string().min(1, 'Password is required');
const strongPasswordSchema = z.string().min(8, 'Password must be at least 8 characters'); const strongPasswordSchema = z.string().min(8, 'Password must be at least 8 characters');
+1 -3
View File
@@ -1,7 +1,5 @@
import { z } from 'zod';
import type { components } from './openapi/schema'; import type { components } from './openapi/schema';
import { commonQuerySchema, stringArrayCoerce, stringCoerce } from './common'; import { commonQuerySchema, stringArrayCoerce } from './common';
export type Author = components['schemas']['AuthorRead']; export type Author = components['schemas']['AuthorRead'];
+1 -1
View File
@@ -67,7 +67,7 @@ const identifiersSchema = z
.transform((str, ctx) => { .transform((str, ctx) => {
try { try {
return JSON.parse(str); return JSON.parse(str);
} catch (e) { } catch {
ctx.addIssue({ ctx.addIssue({
code: 'custom', code: 'custom',
message: 'Must be a valid JSON string' message: 'Must be a valid JSON string'
-2
View File
@@ -1,5 +1,3 @@
import { z } from 'zod';
import type { components } from './openapi/schema'; import type { components } from './openapi/schema';
import { commonQuerySchema, stringArrayCoerce } from './common'; import { commonQuerySchema, stringArrayCoerce } from './common';
-2
View File
@@ -1,5 +1,3 @@
import { z } from 'zod';
import type { components } from './openapi/schema'; import type { components } from './openapi/schema';
import { commonQuerySchema, stringArrayCoerce } from './common'; import { commonQuerySchema, stringArrayCoerce } from './common';
+3 -3
View File
@@ -21,7 +21,7 @@ export class ApiClient {
return this.request(endpoint); return this.request(endpoint);
} }
async post(endpoint: string, data: any): Promise<Response> { async post(endpoint: string, data: unknown): Promise<Response> {
return this.request(endpoint, { return this.request(endpoint, {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -48,7 +48,7 @@ export class ApiClient {
}); });
} }
async put(endpoint: string, data: any): Promise<Response> { async put(endpoint: string, data: unknown): Promise<Response> {
return this.request(endpoint, { return this.request(endpoint, {
method: 'PUT', method: 'PUT',
headers: { headers: {
@@ -71,7 +71,7 @@ export class ApiClient {
}); });
} }
async patch(endpoint: string, data: any): Promise<Response> { async patch(endpoint: string, data: unknown): Promise<Response> {
return this.request(endpoint, { return this.request(endpoint, {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
@@ -1,5 +1,5 @@
import { getContext, setContext } from 'svelte'; import { getContext, setContext } from 'svelte';
import { goto, pushState, replaceState } from '$app/navigation'; import { goto, replaceState } from '$app/navigation';
import type { Book, DynamicFilterData, FilterOption, PaginatedResponse } from '$lib/schema'; import type { Book, DynamicFilterData, FilterOption, PaginatedResponse } from '$lib/schema';
import { page } from '$app/state'; import { page } from '$app/state';
import { BookOperationsState } from './bookOperations.svelte'; import { BookOperationsState } from './bookOperations.svelte';
@@ -16,7 +16,7 @@ export class BookOperationsState {
// Delete dialog related state // Delete dialog related state
deleteDialogOpen = $state(false); deleteDialogOpen = $state(false);
deleteDialogTitle = $state('Delete books?'); deleteDialogTitle = $state('Delete books?');
deleteFn = $state((deleteFiles: boolean) => {}); deleteFn = $state((_deleteFiles: boolean) => {});
// Edit dialog related state // Edit dialog related state
editDialogOpen = $state(false); editDialogOpen = $state(false);
+4 -4
View File
@@ -1,4 +1,4 @@
import { goto, invalidate } from '$app/navigation'; import { invalidate } from '$app/navigation';
import { addBooksToShelf, createBookshelf, listBookshelves, removeBooksFromShelf } from '$lib/api'; import { addBooksToShelf, createBookshelf, listBookshelves, removeBooksFromShelf } from '$lib/api';
import type { Book, Bookshelf } from '$lib/schema'; import type { Book, Bookshelf } from '$lib/schema';
import { getContext, setContext } from 'svelte'; import { getContext, setContext } from 'svelte';
@@ -22,7 +22,7 @@ export class BookshelfState {
async fetchBookshelves(libraryId: string) { async fetchBookshelves(libraryId: string) {
try { try {
let paginatedBookshelves = await listBookshelves({ const paginatedBookshelves = await listBookshelves({
libraries: [libraryId] libraries: [libraryId]
}); });
@@ -34,7 +34,7 @@ export class BookshelfState {
async addBookshelf(name: string, libraryId?: string | number, booksToAdd?: string[] | number[]) { async addBookshelf(name: string, libraryId?: string | number, booksToAdd?: string[] | number[]) {
try { try {
let bookshelf = await createBookshelf({ const bookshelf = await createBookshelf({
title: name, title: name,
library_id: libraryId, library_id: libraryId,
book_ids: booksToAdd book_ids: booksToAdd
@@ -133,7 +133,7 @@ export class BookshelfState {
decrementBy++; decrementBy++;
} }
}); });
return { ...shelf, total: shelf.total - decrementBy }; return { ...shelf, total: (shelf.total ?? 0) - decrementBy };
} }
return shelf; return shelf;
}); });
+1 -1
View File
@@ -1,6 +1,6 @@
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { goto, invalidate } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/state'; import { page } from '$app/state';
import { deleteLibrary } from '$lib/api'; import { deleteLibrary } from '$lib/api';
import type { Library } from '$lib/schema'; import type { Library } from '$lib/schema';
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import BookDelete from '$lib/components/forms/book-delete.svelte'; import BookDelete from '$lib/components/forms/book-delete.svelte';
import { BookEdit } from '$lib/components/forms/edit-book'; import { BookEdit } from '$lib/components/forms/edit-book';
import { getBookOperationsState, setBookOperationsState } from '$lib/state/bookOperations.svelte'; import { getBookOperationsState } from '$lib/state/bookOperations.svelte';
import { getLibraryState } from '$lib/state/library.svelte'; import { getLibraryState } from '$lib/state/library.svelte';
import { setBookSelectionState } from '$lib/state/bookSelection.svelte'; import { setBookSelectionState } from '$lib/state/bookSelection.svelte';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
@@ -28,22 +28,16 @@
import { getBookOperationsState } from '$lib/state/bookOperations.svelte.js'; import { getBookOperationsState } from '$lib/state/bookOperations.svelte.js';
import { getBookshelfState } from '$lib/state/bookshelf.svelte.js'; import { getBookshelfState } from '$lib/state/bookshelf.svelte.js';
import { getLibraryState } from '$lib/state/library.svelte.js'; import { getLibraryState } from '$lib/state/library.svelte.js';
import { Progress } from '$lib/components/ui/progress/index.js';
import * as Accordion from '$lib/components/ui/accordion/index.js'; import * as Accordion from '$lib/components/ui/accordion/index.js';
import * as Table from '$lib/components/ui/table/index.js'; import * as Table from '$lib/components/ui/table/index.js';
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js'; import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
import ShelfCreateDialog from '$lib/components/forms/shelf-create-dialog.svelte'; import ShelfCreateDialog from '$lib/components/forms/shelf-create-dialog.svelte';
import { untrack } from 'svelte';
let { data } = $props(); let { data } = $props();
// Seeded once, then kept in sync by the effect below — untrack says so // Follows the loaded book, and stays assignable so an edit can update it locally
// explicitly rather than capturing the initial value and warning about it. // until the next navigation supplies a fresh one.
let book = $state(untrack(() => data.book)); let book = $derived(data.book);
$effect(() => {
book = data.book;
});
const bookOps = getBookOperationsState(); const bookOps = getBookOperationsState();
const libraryState = getLibraryState(); const libraryState = getLibraryState();
+2 -4
View File
@@ -1,9 +1,7 @@
<script lang="ts"> <script lang="ts">
import { listBookshelves } from '$lib/api';
import AppSidebar from '$lib/components/layout/app-sidebar.svelte'; import AppSidebar from '$lib/components/layout/app-sidebar.svelte';
import SiteHeader from '$lib/components/layout/site-header.svelte'; import SiteHeader from '$lib/components/layout/site-header.svelte';
import UploadTray from '$lib/components/layout/upload-tray.svelte'; import UploadTray from '$lib/components/layout/upload-tray.svelte';
import { Loading } from '$lib/components/ui/command';
import * as Sidebar from '$lib/components/ui/sidebar/index.js'; import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import type { Library, PaginatedResponse } from '$lib/schema'; import type { Library, PaginatedResponse } from '$lib/schema';
import { setBookOperationsState } from '$lib/state/bookOperations.svelte'; import { setBookOperationsState } from '$lib/state/bookOperations.svelte';
@@ -28,8 +26,8 @@
// init. `untrack` says that explicitly, instead of silently capturing the // init. `untrack` says that explicitly, instead of silently capturing the
// initial value and warning about it. // initial value and warning about it.
const libraryState = setLibraryState(untrack(() => data.libraries.items)); const libraryState = setLibraryState(untrack(() => data.libraries.items));
const bookshelfState = setBookshelfState(); setBookshelfState();
const bookOps = setBookOperationsState(libraryState.activeLibrary!.id); setBookOperationsState(libraryState.activeLibrary!.id);
const theme = setThemeState(untrack(() => data.theme)); const theme = setThemeState(untrack(() => data.theme));
// Set here rather than beside the upload dialog so a running import survives // Set here rather than beside the upload dialog so a running import survives
@@ -41,7 +41,7 @@
of libraries; the row above it is overflow-hidden, so without this a of libraries; the row above it is overflow-hidden, so without this a
long list is clipped rather than reachable. --> long list is clipped rather than reachable. -->
<nav class="flex w-48 shrink-0 flex-col gap-1 overflow-y-auto"> <nav class="flex w-48 shrink-0 flex-col gap-1 overflow-y-auto">
{#each items as item} {#each items as item (item.routeId)}
<a <a
href={resolve(item.routeId)} href={resolve(item.routeId)}
class="rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-muted {page class="rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-muted {page
+1 -3
View File
@@ -1,8 +1,6 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
export async function load({ parent }) { export async function load() {
const { libraries } = await parent();
// Immediately redirect to the account settings // Immediately redirect to the account settings
redirect(303, `/settings/account`); redirect(303, `/settings/account`);
} }
@@ -130,7 +130,7 @@
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>
{#each data.devices as device} {#each data.devices as device (device.id)}
{@const isVisible = visibleApiKeys.has(String(device.id))} {@const isVisible = visibleApiKeys.has(String(device.id))}
<Table.Row class="h-14"> <Table.Row class="h-14">
<Table.Cell class="pl-4 font-medium"> <Table.Cell class="pl-4 font-medium">
@@ -31,7 +31,7 @@
</Table.Row> </Table.Row>
</Table.Header> </Table.Header>
<Table.Body> <Table.Body>
{#each libraryState.libraries as library} {#each libraryState.libraries as library (library.id)}
<Table.Row class="h-14"> <Table.Row class="h-14">
<Table.Cell class="w-16 pl-4 text-center text-lg font-semibold" <Table.Cell class="w-16 pl-4 text-center text-lg font-semibold"
>{library.name[0]}</Table.Cell >{library.name[0]}</Table.Cell
+1 -1
View File
@@ -137,7 +137,7 @@ export const DELETE: RequestHandler = async ({ params, locals, fetch, request, u
const headers = prepareRequest(locals, request); const headers = prepareRequest(locals, request);
// DELETE may or may not have a body // DELETE may or may not have a body
let options: RequestInit = { const options: RequestInit = {
method: 'DELETE', method: 'DELETE',
headers headers
}; };
-2
View File
@@ -1,9 +1,7 @@
<script lang="ts"> <script lang="ts">
import { replaceState } from '$app/navigation';
import LoginForm from '$lib/components/forms/login-form.svelte'; import LoginForm from '$lib/components/forms/login-form.svelte';
import SignupForm from '$lib/components/forms/signup-form.svelte'; import SignupForm from '$lib/components/forms/signup-form.svelte';
import * as Tabs from '$lib/components/ui/tabs/index.js'; import * as Tabs from '$lib/components/ui/tabs/index.js';
import { redirect } from '@sveltejs/kit';
let tabValue = $state('login'); let tabValue = $state('login');
</script> </script>