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:
68
frontend/src/lib/api/page_collections.ts
Normal file
68
frontend/src/lib/api/page_collections.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { request, type Page } from './client';
|
||||
|
||||
/** Row returned by `GET /v1/collections/:id/pages`. */
|
||||
export type CollectionPageItem = {
|
||||
page_id: string;
|
||||
chapter_id: string;
|
||||
manga_id: string;
|
||||
page_number: number;
|
||||
chapter_number: number;
|
||||
chapter_title: string | null;
|
||||
manga_title: string;
|
||||
storage_key: string;
|
||||
added_at: string;
|
||||
};
|
||||
|
||||
export type CollectionPagesPage = {
|
||||
items: CollectionPageItem[];
|
||||
page: Page;
|
||||
};
|
||||
|
||||
export type ListOptions = { limit?: number; offset?: number };
|
||||
|
||||
export async function listCollectionPages(
|
||||
collectionId: string,
|
||||
opts: ListOptions = {}
|
||||
): Promise<CollectionPagesPage> {
|
||||
const params = new URLSearchParams();
|
||||
if (opts.limit != null) params.set('limit', String(opts.limit));
|
||||
if (opts.offset != null) params.set('offset', String(opts.offset));
|
||||
const qs = params.toString();
|
||||
return request<CollectionPagesPage>(
|
||||
`/v1/collections/${encodeURIComponent(collectionId)}/pages${qs ? `?${qs}` : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
export async function addPageToCollection(
|
||||
collectionId: string,
|
||||
pageId: string
|
||||
): Promise<void> {
|
||||
await request<void>(
|
||||
`/v1/collections/${encodeURIComponent(collectionId)}/pages`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ page_id: pageId })
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function removePageFromCollection(
|
||||
collectionId: string,
|
||||
pageId: string
|
||||
): Promise<void> {
|
||||
await request<void>(
|
||||
`/v1/collections/${encodeURIComponent(collectionId)}/pages/${encodeURIComponent(pageId)}`,
|
||||
{ method: 'DELETE' }
|
||||
);
|
||||
}
|
||||
|
||||
/** Which of the user's collections currently contain this page. */
|
||||
export async function getMyCollectionsContainingPage(
|
||||
pageId: string
|
||||
): Promise<string[]> {
|
||||
const r = await request<{ collection_ids: string[] }>(
|
||||
`/v1/pages/${encodeURIComponent(pageId)}/my-collections`
|
||||
);
|
||||
return r.collection_ids;
|
||||
}
|
||||
Reference in New Issue
Block a user