feat(search): tag-based page search surface + per-page tags & collections

Add the /search surface (Pages / Chapters / Mangas tabs) backed by
per-user page tags and per-page collections: schema (migration 0023),
backend endpoints for page tags/collections and tagged-page aggregations
(with the OCR text-search param reserved at 501), plus the frontend API
clients, library Page-tags tab, collection page sections, page context
menu / AddTagsSheet, and reader long-press wiring. Includes the
continuous-reader navigation fixes (?page=N handling, chapter-reset
timing, back-button pops history) and tag-normalization hardening
accumulated on the branch.

Bump version 0.60.2 -> 0.62.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-13 15:51:38 +02:00
parent 9910a0a995
commit 6c901e64c9
50 changed files with 6971 additions and 132 deletions

View File

@@ -18,6 +18,7 @@
import Search from '@lucide/svelte/icons/search';
import SlidersHorizontal from '@lucide/svelte/icons/sliders-horizontal';
import ArrowUpDown from '@lucide/svelte/icons/arrow-up-down';
import ArrowRight from '@lucide/svelte/icons/arrow-right';
import Plus from '@lucide/svelte/icons/plus';
const PAGE_SIZE = 50;
@@ -402,7 +403,22 @@
{/if}
{/snippet}
<h1>Mangas</h1>
<div class="heading-row">
<h1>Mangas</h1>
<!--
Secondary entry point into the per-page search at /search,
which is headed "Page search". Label matches the destination's
title so the two read as one feature. It filters the user's
tagged pages and pivots between Pages / Chapters / Mangas;
title-search above stays the primary action. The name is also
forward-compatible with the planned OCR text search (which
searches page content, not just tags).
-->
<a class="alt-search" href="/search" data-testid="nav-page-search">
<span>Page search</span>
<ArrowRight size={14} aria-hidden="true" />
</a>
</div>
<form
onsubmit={onSubmit}
@@ -562,11 +578,38 @@
{/if}
<style>
.heading-row {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-3);
flex-wrap: wrap;
}
.heading-row h1 {
margin-bottom: 0;
}
.alt-search {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--text-muted);
font-size: var(--font-sm);
text-decoration: none;
white-space: nowrap;
}
.alt-search:hover {
color: var(--primary);
}
.controls {
display: flex;
flex-direction: column;
gap: var(--space-3);
margin-bottom: var(--space-4);
margin-top: var(--space-3);
}
.search-row {