diff --git a/frontend/package.json b/frontend/package.json index 69c04d9..70affe3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "mangalord-frontend", - "version": "0.126.0", + "version": "0.127.0", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/lib/api/page_tags.test.ts b/frontend/src/lib/api/page_tags.test.ts index 8c15911..dfcb95e 100644 --- a/frontend/src/lib/api/page_tags.test.ts +++ b/frontend/src/lib/api/page_tags.test.ts @@ -165,6 +165,16 @@ describe('page_tags api client', () => { expect(url).toMatch(/\/v1\/me\/page-tags\/mangas\?tag=funny$/); }); + it('aggregation calls forward the OCR text filter when provided', async () => { + fetchSpy.mockResolvedValueOnce( + ok({ items: [], page: { limit: 50, offset: 0, total: 0 } }) + ); + await listTaggedChapters({ tag: 'funny', text: 'hello world' }); + const url = fetchSpy.mock.calls[0][0] as string; + expect(url).toContain('tag=funny'); + expect(url).toContain('text=hello+world'); + }); + it('searchPages serializes tags (CSV), text and content-warning filters', async () => { fetchSpy.mockResolvedValueOnce( ok({ items: [], page: { limit: 50, offset: 0, total: 0 } }) diff --git a/frontend/src/lib/api/page_tags.ts b/frontend/src/lib/api/page_tags.ts index 512aec3..b339129 100644 --- a/frontend/src/lib/api/page_tags.ts +++ b/frontend/src/lib/api/page_tags.ts @@ -179,6 +179,9 @@ export type AggregateOptions = { order?: 'desc' | 'asc'; limit?: number; offset?: number; + /** OCR full-text filter: when set, only pages whose analysis search_doc + * matches are aggregated (and `match_count` reflects that). */ + text?: string; }; function aggregateQs(opts: AggregateOptions): string { @@ -187,6 +190,7 @@ function aggregateQs(opts: AggregateOptions): string { if (opts.order != null) params.set('order', opts.order); if (opts.limit != null) params.set('limit', String(opts.limit)); if (opts.offset != null) params.set('offset', String(opts.offset)); + if (opts.text != null && opts.text !== '') params.set('text', opts.text); return params.toString(); } diff --git a/frontend/src/routes/search/+page.ts b/frontend/src/routes/search/+page.ts index 7f05381..5842e39 100644 --- a/frontend/src/routes/search/+page.ts +++ b/frontend/src/routes/search/+page.ts @@ -103,26 +103,44 @@ export const load: PageLoad = async ({ url }) => { // every tag / view / sort / text change instead of freezing the old list. const results = (async () => { try { - // Content search takes precedence over tag browsing. + // Chapters / Mangas are tag-scoped aggregation views. When a tag is + // selected, run the aggregation — optionally filtered by the OCR + // `text` — so the selected view AND tag are honoured. Previously any + // `text` collapsed every view into a flat page search, discarding + // both the tag and the chapters/mangas grouping. + if (tag && view === 'chapters') { + const r = await listTaggedChapters({ + tag, + order, + limit: 100, + text: text || undefined + }); + return { ...emptyResults, chapters: r.items, total: r.page.total ?? 0 }; + } + if (tag && view === 'mangas') { + const r = await listTaggedMangas({ + tag, + order, + limit: 100, + text: text || undefined + }); + return { ...emptyResults, mangas: r.items, total: r.page.total ?? 0 }; + } + // Pages view (or a content search with no tag): a positive filter + // runs the page search — intersecting the selected tag with the OCR + // text / content-warning filters instead of ignoring the tag. if (contentSearch) { const r = await searchPages({ text: text || undefined, + tags: tag ? [tag] : undefined, cwInclude, cwExclude, limit: 100 }); return { ...emptyResults, results: r.items, total: r.page.total ?? 0 }; } - // No tag selected → just the chip cloud, no results. + // No tag selected and no content filter → just the chip cloud. if (!tag) return emptyResults; - if (view === 'chapters') { - const r = await listTaggedChapters({ tag, order, limit: 100 }); - return { ...emptyResults, chapters: r.items, total: r.page.total ?? 0 }; - } - if (view === 'mangas') { - const r = await listTaggedMangas({ tag, order, limit: 100 }); - return { ...emptyResults, mangas: r.items, total: r.page.total ?? 0 }; - } const r = await listMyPageTags({ tag, limit: 100 }); return { ...emptyResults, pages: r.items, total: r.page.total ?? 0 }; } catch (e) {