feat: opt-in Journal search page and JournalSearchBox
- journal.routes.search (off by default) registers journal-search, the results page for ?q= in the API's relevance order (no sort is sent), server-rendered with a Polish-aware result count. - JournalSearchBox: an icon button and a WAI-ARIA combobox with debounced ?fields=suggest suggestions, stale answers dropped, query words marked ignoring diacritics; a plain GET form without JS. - The module keeps the search path out of the route cache and the sitemap and marks it noindex, follow. - Client: suggestPosts and the fields option; useJournalLinks.searchLink.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// A small node:http stand-in for the anonymous Journal API
|
||||
// (/_journal/api/v1), answering from JSON fixtures. It follows the Go
|
||||
// handlers' query rules (page, per_page clamp, sort allow-list, category with
|
||||
// nested children, tag, search, include_empty) and the author routes, and it
|
||||
// nested children, tag, search, fields=suggest, include_empty) and the author routes, and it
|
||||
// records every request so a smoke run can assert header forwarding.
|
||||
//
|
||||
// Plain .mjs with JSDoc types: Node does not strip TypeScript under
|
||||
@@ -106,6 +106,19 @@ function paginate(rows, page, perPage) {
|
||||
}
|
||||
}
|
||||
|
||||
/** The ?fields=suggest row of the Go serializer. @param {any} post */
|
||||
function suggestion(post) {
|
||||
const text = String(post.excerpt || post.content_html || '').replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim()
|
||||
return {
|
||||
id: post.id,
|
||||
title: post.title,
|
||||
slug: post.slug,
|
||||
excerpt: text.length > 160 ? `${text.slice(0, 160).replace(/\s+\S*$/, '')}…` : text,
|
||||
featured_image_url: post.featured_image_url ?? null,
|
||||
published_at: post.published_at,
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {any} post */
|
||||
function sibling(post) {
|
||||
return post ? { id: post.id, title: post.title, slug: post.slug } : null
|
||||
@@ -172,7 +185,9 @@ export function startFixtureApi({ dir, port = 0 }) {
|
||||
rows = rows.filter((p) => p.title.toLowerCase().includes(s))
|
||||
}
|
||||
rows = sortRows(rows, parseSort(q.get('sort')))
|
||||
return paginate(rows, parsePage(q.get('page')), parsePerPage(q.get('per_page')))
|
||||
const out = paginate(rows, parsePage(q.get('page')), parsePerPage(q.get('per_page')))
|
||||
if (q.get('fields') !== 'suggest') return out
|
||||
return { ...out, data: out.data.map(suggestion) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user