feat(search): frontend content search (text + warnings) + manga warning banner
Surfaces the analysis pipeline in the UI: - api clients: searchPages() + PageSearchItem/ContentWarning types; listMangas cwInclude/cwExclude; MangaDetail.content_warnings. - /search gains a content-search mode: free-text over OCR+scene and tri-state content-warning toggles (include/exclude), driven by URL params (text, cw_include, cw_exclude) and the new /me/page-search endpoint. Tag browsing is preserved when no content filter is active. - manga detail renders a deduped content-warning banner. Tests: vitest param serialization (searchPages CSV/text/cw, listMangas cw); Playwright text-search + cw-toggle flows (route-mocked). svelte-check + build clean; full vitest (258) + search e2e (7) green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
import { resyncManga } from '$lib/api/admin';
|
||||
import { chapterLabel } from '$lib/api/chapters';
|
||||
import { listTags, type Tag } from '$lib/api/tags';
|
||||
import type { ContentWarning } from '$lib/api/page_tags';
|
||||
import { session } from '$lib/session.svelte';
|
||||
import Chip from '$lib/components/Chip.svelte';
|
||||
import MangaCard from '$lib/components/MangaCard.svelte';
|
||||
@@ -62,6 +63,8 @@
|
||||
|
||||
const authors = $derived<AuthorRef[]>(manga.authors);
|
||||
const genres = $derived<GenreRef[]>(manga.genres);
|
||||
/** Deduped content warnings across the manga's pages (analysis worker). */
|
||||
const contentWarnings = $derived<ContentWarning[]>(manga.content_warnings ?? []);
|
||||
|
||||
/** Up to 5 mangas with the most similar tags; empty hides the section. */
|
||||
const similar = $derived(data.similar);
|
||||
@@ -395,6 +398,15 @@
|
||||
</details>
|
||||
{/if}
|
||||
|
||||
{#if contentWarnings.length > 0}
|
||||
<div class="content-warnings" data-testid="manga-content-warnings" role="note">
|
||||
<span class="cw-label">⚠ Content warning</span>
|
||||
{#each contentWarnings as w (w)}
|
||||
<span class="cw-chip">{w}</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if genres.length > 0}
|
||||
<div class="chip-row" data-testid="manga-genres">
|
||||
<span class="chip-row-label">Genres</span>
|
||||
@@ -785,6 +797,33 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content-warnings {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
margin: var(--space-3) 0;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
border: 1px solid color-mix(in srgb, #d4762a 50%, transparent);
|
||||
border-radius: var(--radius-md, 8px);
|
||||
background: color-mix(in srgb, #d4762a 12%, transparent);
|
||||
}
|
||||
|
||||
.cw-label {
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 600;
|
||||
color: #b85e1a;
|
||||
}
|
||||
|
||||
.cw-chip {
|
||||
font-size: var(--font-sm);
|
||||
padding: 2px var(--space-2);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
background: color-mix(in srgb, #d4762a 22%, transparent);
|
||||
color: var(--text);
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.tag-form {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user