(search)(sources) add search for sources

This commit is contained in:
Valentyne Stigloher 2024-12-16 23:36:03 +01:00
parent 9f7a54cf36
commit c9378e9659
3 changed files with 113 additions and 1 deletions

View File

@ -0,0 +1,29 @@
<script setup lang="ts">
import type { SearchResultSource } from '~/server/api/search.get.ts';
defineProps<{
result: SearchResultSource;
}>();
</script>
<template>
<nuxt-link :to="result.url" class="text-dark">
<div class="h3">
<Icon v="books" />
<Spelling :text="result.title" />
</div>
<div class="d-flex">
<div class="col-2">
<img
v-if="result.image"
:src="result.image"
class="w-100 pe-2 border rounded-2"
loading="lazy"
>
</div>
<div class="col">
<Spelling :text="result.content" />
</div>
</div>
</nuxt-link>
</template>

View File

@ -34,6 +34,7 @@ const searchInput = useTemplateRef('searchInput');
<li v-for="result of results.data.value" :key="`${result.type}-${result.id}`" class="list-group-item">
<SearchItemPronoun v-if="result.type === 'pronoun'" :result="result" />
<SearchItemNoun v-else-if="result.type === 'noun'" :result="result" />
<SearchItemSource v-else-if="result.type === 'source'" :result="result" />
<SearchItemBlog v-else-if="result.type === 'blog'" :result="result" />
</li>
</ul>

View File

@ -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<SearchDocumentNoun, SearchResultNoun>
}
}
interface SearchDocumentSource {
id: number;
type: SearchIndexSource['TYPE'];
url: string;
title: string;
image: string | undefined;
content: string;
}
export type SearchResultSource = SearchDocumentSource;
class SearchIndexSource extends SearchIndex<SearchDocumentSource, SearchResultSource> {
TYPE = 'source' as const;
constructor() {
super(['url', 'title', 'content']);
}
async getDocuments(config: Config): Promise<SearchDocumentSource[]> {
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(/(?<!\\)@/g, '; ');
const images = source.images ? source.images.split(',') : [];
const image = images.length > 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]),
);