feat(search): „+ weitere Ergebnisse"-Button für lokale und Web-Suche
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m20s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m20s
Die Ergebnislisten waren oft kurz, weil lokale Suche auf LIMIT 30 und die Web-Suche auf die erste SearXNG-Seite beschränkt war. Jetzt lässt sich beides nachladen. - `searchLocal` nimmt jetzt einen `offset` und der `/api/recipes/search`- Endpoint einen `?offset=`-Parameter. - `searchWeb` nimmt jetzt eine `pageno`-Option und reicht sie als `pageno`-Parameter an SearXNG weiter. `pageno=1` wird weggelassen, damit bestehendes Verhalten unverändert bleibt. - `/search` und `/search/web` zeigen unterhalb der Liste einen „+ weitere Ergebnisse"-Button. Beide deduplizieren nachgeladene Hits (ID bzw. URL), weil SearXNG das gleiche Ergebnis auf zwei Seiten liefern kann. Kein Endless-Scroll: expliziter Button ist mobil robuster und spart die teure Thumbnail-Enrichment-Roundtrip-Zeit, die bei jeder neuen Web-Seite anfällt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,20 @@ describe('searchLocal', () => {
|
||||
expect(searchLocal(db, ' ')).toEqual([]);
|
||||
});
|
||||
|
||||
it('paginates via limit + offset', () => {
|
||||
const db = openInMemoryForTest();
|
||||
for (let i = 0; i < 5; i++) {
|
||||
insertRecipe(db, recipe({ title: `Pizza ${i}` }));
|
||||
}
|
||||
const first = searchLocal(db, 'pizza', 2, 0);
|
||||
const second = searchLocal(db, 'pizza', 2, 2);
|
||||
expect(first.length).toBe(2);
|
||||
expect(second.length).toBe(2);
|
||||
// No overlap between pages
|
||||
const firstIds = new Set(first.map((h) => h.id));
|
||||
for (const h of second) expect(firstIds.has(h.id)).toBe(false);
|
||||
});
|
||||
|
||||
it('aggregates avg_stars across profiles', () => {
|
||||
const db = openInMemoryForTest();
|
||||
const id = insertRecipe(db, recipe({ title: 'Rated' }));
|
||||
|
||||
@@ -72,6 +72,34 @@ describe('searchWeb', () => {
|
||||
expect(hits).toEqual([]);
|
||||
});
|
||||
|
||||
it('passes pageno to SearXNG when > 1', async () => {
|
||||
const db = openInMemoryForTest();
|
||||
addDomain(db, 'chefkoch.de');
|
||||
let receivedPageno: string | null = 'not set';
|
||||
server.on('request', (req, res) => {
|
||||
const u = new URL(req.url ?? '/', 'http://localhost');
|
||||
receivedPageno = u.searchParams.get('pageno');
|
||||
res.writeHead(200, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ results: [] }));
|
||||
});
|
||||
await searchWeb(db, 'x', { searxngUrl: baseUrl, enrichThumbnails: false, pageno: 3 });
|
||||
expect(receivedPageno).toBe('3');
|
||||
});
|
||||
|
||||
it('omits pageno param when 1', async () => {
|
||||
const db = openInMemoryForTest();
|
||||
addDomain(db, 'chefkoch.de');
|
||||
let receivedPageno: string | null = 'not set';
|
||||
server.on('request', (req, res) => {
|
||||
const u = new URL(req.url ?? '/', 'http://localhost');
|
||||
receivedPageno = u.searchParams.get('pageno');
|
||||
res.writeHead(200, { 'content-type': 'application/json' });
|
||||
res.end(JSON.stringify({ results: [] }));
|
||||
});
|
||||
await searchWeb(db, 'x', { searxngUrl: baseUrl, enrichThumbnails: false });
|
||||
expect(receivedPageno).toBe(null);
|
||||
});
|
||||
|
||||
it('enriches missing thumbnails from og:image', async () => {
|
||||
const pageServer = createServer((_req, res) => {
|
||||
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
||||
|
||||
Reference in New Issue
Block a user