diff --git a/components/search/SearchItemNoun.vue b/components/search/SearchItemNoun.vue new file mode 100644 index 000000000..7247c5e9b --- /dev/null +++ b/components/search/SearchItemNoun.vue @@ -0,0 +1,17 @@ + + + diff --git a/pages/search.vue b/pages/search.vue index 3dad62b96..a4e8d141b 100644 --- a/pages/search.vue +++ b/pages/search.vue @@ -33,6 +33,7 @@ const searchInput = useTemplateRef('searchInput'); diff --git a/server/api/search.get.ts b/server/api/search.get.ts index 54a18b1e7..136e4d693 100644 --- a/server/api/search.get.ts +++ b/server/api/search.get.ts @@ -7,9 +7,11 @@ import type { MatchInfo, SearchResult } from 'minisearch'; import type { Config } from '~/locale/config.ts'; import { getPosts, type PostMetadata } from '~/server/blog.ts'; +import { getNounEntries } from '~/server/express/nouns.ts'; import { loadSuml, loadSumlFromBase } from '~/server/loader.ts'; import { rootDir } from '~/server/paths.ts'; import { parsePronouns } from '~/src/buildPronoun.ts'; +import { genders, gendersWithNumerus } from '~/src/classes.ts'; import { clearLinkedText } from '~/src/helpers.ts'; import parseMarkdown from '~/src/parseMarkdown.ts'; import { Translator } from '~/src/translator.ts'; @@ -136,6 +138,62 @@ class SearchIndexPronoun extends SearchIndex { + TYPE = 'noun' as const; + + constructor() { + super(['url', 'title', 'content']); + } + + async getDocuments(config: Config): Promise { + if (!config.nouns.enabled) { + return []; + } + + const base = encodeURIComponent(config.nouns.route); + + const db = useDatabase(); + const nouns = await getNounEntries(db, () => false); + return nouns.map((noun, id): SearchDocumentNoun => { + const firstWords = genders + .filter((gender) => noun[gender]) + .map((gender) => noun[gender].split('|')[0]); + return { + id, + type: this.TYPE, + url: `/${base}?filter=${firstWords[0]}`, + title: firstWords.join(' – '), + content: gendersWithNumerus + .filter((genderWithNumerus) => noun[genderWithNumerus]) + .map((genderWithNumerus) => noun[genderWithNumerus].replaceAll('|', ', ')) + .join(' – '), + }; + }); + } + + override transform(result: SearchResult): SearchResultNoun { + const document = this.documents[result.id]; + const termsByField = getTermsByField(result.match); + return { + id: document.id, + type: document.type, + url: document.url, + title: highlightMatches(document.title, termsByField.title), + content: highlightMatches(document.content, termsByField.content, true), + }; + } +} + interface SearchDocumentBlog extends PostMetadata { id: number; type: SearchIndexBlog['TYPE']; @@ -201,6 +259,7 @@ export default defineEventHandler(async (event) => { const indices = Object.fromEntries( [ new SearchIndexPronoun(), + new SearchIndexNoun(), new SearchIndexBlog(), ].map((index) => [index.TYPE, index]), ); diff --git a/server/express/nouns.ts b/server/express/nouns.ts index 834890cb5..698c24d4e 100644 --- a/server/express/nouns.ts +++ b/server/express/nouns.ts @@ -99,7 +99,7 @@ const selectFragment = (sourcesMap: Record, keyAndFragment: s const router = Router(); -const getNounEntries = defineCachedFunction(async (db: Database, isGranted: Request['isGranted']) => { +export const getNounEntries = defineCachedFunction(async (db: Database, isGranted: Request['isGranted']) => { return await addVersions(db, isGranted, await db.all(SQL` SELECT n.*, u.username AS author FROM nouns n LEFT JOIN users u ON n.author_id = u.id