From c9378e965939ac78ca0116d300d0ade6dd7f4c44 Mon Sep 17 00:00:00 2001 From: Valentyne Stigloher Date: Mon, 16 Dec 2024 23:36:03 +0100 Subject: [PATCH] (search)(sources) add search for sources --- components/search/SearchItemSource.vue | 29 +++++++++ pages/search.vue | 1 + server/api/search.get.ts | 84 +++++++++++++++++++++++++- 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 components/search/SearchItemSource.vue diff --git a/components/search/SearchItemSource.vue b/components/search/SearchItemSource.vue new file mode 100644 index 000000000..adda62972 --- /dev/null +++ b/components/search/SearchItemSource.vue @@ -0,0 +1,29 @@ + + + diff --git a/pages/search.vue b/pages/search.vue index a4e8d141b..f3aa7e6a7 100644 --- a/pages/search.vue +++ b/pages/search.vue @@ -34,6 +34,7 @@ const searchInput = useTemplateRef('searchInput');
  • +
  • diff --git a/server/api/search.get.ts b/server/api/search.get.ts index 136e4d693..7607870c1 100644 --- a/server/api/search.get.ts +++ b/server/api/search.get.ts @@ -8,11 +8,12 @@ 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 { getSourcesEntries } from '~/server/express/sources.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 { clearLinkedText, buildImageUrl } from '~/src/helpers.ts'; import parseMarkdown from '~/src/parseMarkdown.ts'; import { Translator } from '~/src/translator.ts'; import { loadTsv } from '~/src/tsv.ts'; @@ -194,6 +195,86 @@ class SearchIndexNoun extends SearchIndex } } +interface SearchDocumentSource { + id: number; + type: SearchIndexSource['TYPE']; + url: string; + title: string; + image: string | undefined; + content: string; +} + +export type SearchResultSource = SearchDocumentSource; + +class SearchIndexSource extends SearchIndex { + TYPE = 'source' as const; + + constructor() { + super(['url', 'title', 'content']); + } + + async getDocuments(config: Config): Promise { + if (!config.sources.enabled) { + return []; + } + + const runtimeConfig = useRuntimeConfig(); + + const base = encodeURIComponent(config.sources.route); + + const db = useDatabase(); + const sources = await getSourcesEntries(db, () => false, undefined); + return sources.map((source, id): SearchDocumentSource => { + let title = ''; + if (source.author) { + title += `${source.author.replace('^', '')} – `; + } + title += source.title; + if (source.extra) { + title += ` (${source.extra})`; + } + title += `, ${source.year}`; + + let content = ''; + if (source.comment) { + content += `${source.comment} `; + } + content += source.fragments + .replaceAll('[[', '') + .replaceAll(']]', '') + .replaceAll('|', ' ') + .replaceAll(/(? 0 + ? buildImageUrl(runtimeConfig.public.cloudfront, images[0], 'thumb') + : undefined; + + return { + id, + type: this.TYPE, + url: `/${base}?filter=${source.title}`, + title, + image, + content, + }; + }); + } + + override transform(result: SearchResult): SearchResultSource { + 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), + image: document.image, + content: highlightMatches(document.content, termsByField.content, true), + }; + } +} + interface SearchDocumentBlog extends PostMetadata { id: number; type: SearchIndexBlog['TYPE']; @@ -260,6 +341,7 @@ export default defineEventHandler(async (event) => { [ new SearchIndexPronoun(), new SearchIndexNoun(), + new SearchIndexSource(), new SearchIndexBlog(), ].map((index) => [index.TYPE, index]), );