import { sql } from 'drizzle-orm' import type { AccessActor } from './groups.ts' /** * Locale to PostgreSQL text search dictionary, for the languages postgres ships a snowball stemmer * for. Anything not listed here falls back to `simple`, which indexes words without stemming — still * searchable, just without matching plurals and conjugations. * * An operator can override or extend this from the admin area, which is what `dictOverrides` is for. */ export const DEFAULT_DICTIONARIES: Record = { ar: 'arabic', ca: 'catalan', da: 'danish', de: 'german', el: 'greek', en: 'english', es: 'spanish', et: 'estonian', eu: 'basque', fi: 'finnish', fr: 'french', ga: 'irish', hi: 'hindi', hu: 'hungarian', hy: 'armenian', id: 'indonesian', it: 'italian', lt: 'lithuanian', ne: 'nepali', nl: 'dutch', no: 'norwegian', pt: 'portuguese', ro: 'romanian', ru: 'russian', sr: 'serbian', sv: 'swedish', ta: 'tamil', tr: 'turkish', yi: 'yiddish' } /** The dictionary used when a locale has no mapping, or when its mapping is not installed. */ export const FALLBACK_DICTIONARY = 'simple' export interface SearchConfig { termHighlighting: boolean dictOverrides: Record } /** What a rebuild did, per locale, so the caller can report something concrete. */ export interface RebuildResult { pages: number locales: { locale: string; dictionary: string; pages: number }[] } export const SEARCH_ORDER_BY = ['relevancy', 'title', 'updatedAt'] as const export type SearchOrderBy = (typeof SEARCH_ORDER_BY)[number] export interface SearchResult { id: string path: string locale: string title: string description: string | null icon: string | null tags: string[] updatedAt: string relevancy: number highlight: string | null } export interface SearchPagesResult { results: SearchResult[] totalHits: number } export interface SearchPagesParams { siteId: string query?: string path?: string locales?: string[] tags?: string[] editor?: string publishState?: string orderBy?: SearchOrderBy orderByDirection?: 'asc' | 'desc' offset?: number limit?: number /** Restrict to what a reader with no session may see: published pages. */ publicOnly?: boolean /** Whether unpublished pages belong in the results, which is an editor's view of the wiki. */ includeDrafts?: boolean /** * Who is searching, so that a result they could not open never reaches them. * * Applied to the rows rather than in the query: which pages a rule covers can depend on a regular * expression or on a page's tags, neither of which a `WHERE` clause here could express. */ actor?: AccessActor /** * Keep a password-protected page's *body* out of the results, for a searcher who would have to enter * the password to read it. The page itself still appears — its title and description are not what * the password covers — but it can only be matched on those, and comes back with no excerpt. */ hideProtectedContent?: boolean } /** * Markers `ts_headline` wraps a matched term in. * * Control characters, because the excerpt is page text that may itself contain anything: it is HTML * escaped before these are turned into tags, so a page whose text reads `