(refactor) common SearchDocument type and <SearchItem> component for all search categories

This commit is contained in:
Valentyne Stigloher 2024-12-19 18:09:04 +01:00
parent a12b14256d
commit dfc524e888
12 changed files with 147 additions and 449 deletions

View File

@ -0,0 +1,67 @@
<script setup lang="ts">
import type { SearchDocument } from '~/src/search.ts';
const props = defineProps<{
document: SearchDocument;
}>();
const iconBySearchDocumentType: Record<SearchDocument['type'], string> = {
pronoun: 'tags',
noun: 'book',
source: 'books',
link: 'bookmark',
faq: 'map-marker-question',
blog: 'pen-nib',
term: 'flag',
inclusive: 'book-heart',
};
const icon = computed(() => {
return iconBySearchDocumentType[props.document.type];
});
const searchDocumentTypesWithImage: SearchDocument['type'][] = ['source', 'blog', 'term'];
const searchDocumentTypeHasImage = computed(() => {
return searchDocumentTypesWithImage.includes(props.document.type);
});
</script>
<template>
<nuxt-link :to="document.url" class="text-dark">
<div class="h3">
<Icon :v="icon" />
<Spelling :text="document.title" />
</div>
<div v-if="searchDocumentTypeHasImage" class="d-flex">
<div class="col-2">
<img
v-if="document.image"
:src="document.image.src"
:class="['w-100 pe-2', document.image.class]"
:alt="document.image.alt"
loading="lazy"
>
</div>
<div class="col">
<Spelling :text="document.content" />
<ul v-if="document.date || document.authors" class="list-inline mb-0 small">
<li class="list-inline-item small">
<Icon v="calendar" />
{{ document.date }}
</li>
<li v-for="author in document.authors" :key="author" class="list-inline-item">
<span v-if="author.startsWith('@')" class="badge bg-light text-dark border">
<Icon v="collective-logo.svg" class="invertible" />
{{ author }}
</span>
<span v-else class="badge bg-light text-dark border">
{{ author }}
</span>
</li>
</ul>
</div>
</div>
<Spelling v-else :text="document.content" />
</nuxt-link>
</template>

View File

@ -1,45 +0,0 @@
<script setup lang="ts">
import type { SearchResultBlog } from '~/server/api/search.get.ts';
defineProps<{
result: SearchResultBlog;
}>();
</script>
<template>
<nuxt-link :to="result.url" class="text-dark">
<div class="h3">
<Icon v="pen-nib" />
<Spelling :text="result.title" />
</div>
<div class="d-flex">
<div class="col-2">
<img
v-if="result.hero"
:src="result.hero.src"
:class="['w-100 pe-2', result.hero.class]"
:alt="result.hero.alt"
loading="lazy"
>
</div>
<div class="col">
<Spelling :text="result.content" />
<ul class="list-inline mb-0 small">
<li class="list-inline-item small">
<Icon v="calendar" />
{{ result.date }}
</li>
<li v-for="author in result.authors" :key="author" class="list-inline-item">
<span v-if="author.startsWith('@')" class="badge bg-light text-dark border">
<Icon v="collective-logo.svg" class="invertible" />
{{ author }}
</span>
<span v-else class="badge bg-light text-dark border">
{{ author }}
</span>
</li>
</ul>
</div>
</div>
</nuxt-link>
</template>

View File

@ -1,17 +0,0 @@
<script setup lang="ts">
import type { SearchResultFaq } from '~/server/api/search.get.ts';
defineProps<{
result: SearchResultFaq;
}>();
</script>
<template>
<nuxt-link :to="result.url" class="text-dark">
<div class="h3">
<Icon v="map-marker-question" />
<Spelling :text="result.title" />
</div>
<Spelling :text="result.content" />
</nuxt-link>
</template>

View File

@ -1,17 +0,0 @@
<script setup lang="ts">
import type { SearchResultInclusive } from '~/server/api/search.get.ts';
defineProps<{
result: SearchResultInclusive;
}>();
</script>
<template>
<nuxt-link :to="result.url" class="text-dark">
<div class="h3">
<Icon v="book-heart" />
<Spelling :text="result.title" />
</div>
<Spelling :text="result.content" />
</nuxt-link>
</template>

View File

@ -1,17 +0,0 @@
<script setup lang="ts">
import type { SearchResultLink } from '~/server/api/search.get.ts';
defineProps<{
result: SearchResultLink;
}>();
</script>
<template>
<nuxt-link :to="result.url" class="text-dark">
<div class="h3">
<Icon v="bookmark" />
<Spelling :text="result.title" />
</div>
<Spelling :text="result.content" />
</nuxt-link>
</template>

View File

@ -1,17 +0,0 @@
<script setup lang="ts">
import type { SearchResultNoun } from '~/server/api/search.get.ts';
defineProps<{
result: SearchResultNoun;
}>();
</script>
<template>
<nuxt-link :to="result.url" class="text-dark">
<div class="h3">
<Icon v="book" />
<Spelling :text="result.title" />
</div>
<Spelling :text="result.content" />
</nuxt-link>
</template>

View File

@ -1,17 +0,0 @@
<script setup lang="ts">
import type { SearchResultPronoun } from '~/server/api/search.get.ts';
defineProps<{
result: SearchResultPronoun;
}>();
</script>
<template>
<nuxt-link :to="result.url" class="text-dark">
<div class="h3">
<Icon v="tags" class="h3" />
<strong><Spelling :text="result.short" /></strong><small v-if="result.small">/<Spelling :text="result.small" /></small>
</div>
<Spelling :text="result.content" />
</nuxt-link>
</template>

View File

@ -1,29 +0,0 @@
<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

@ -1,29 +0,0 @@
<script setup lang="ts">
import type { SearchResultTerm } from '~/server/api/search.get.ts';
defineProps<{
result: SearchResultTerm;
}>();
</script>
<template>
<nuxt-link :to="result.url" class="text-dark">
<div class="h3">
<Icon v="flag" />
<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"
loading="lazy"
>
</div>
<div class="col">
<Spelling :text="result.content" />
</div>
</div>
</nuxt-link>
</template>

View File

@ -31,15 +31,12 @@ const searchInput = useTemplateRef('searchInput');
</section> </section>
<section> <section>
<ul class="list-group"> <ul class="list-group">
<li v-for="result of results.data.value" :key="`${result.type}-${result.id}`" class="list-group-item"> <li
<SearchItemPronoun v-if="result.type === 'pronoun'" :result="result" /> v-for="document of results.data.value"
<SearchItemNoun v-else-if="result.type === 'noun'" :result="result" /> :key="`${document.type}-${document.id}`"
<SearchItemSource v-else-if="result.type === 'source'" :result="result" /> class="list-group-item"
<SearchItemLink v-else-if="result.type === 'link'" :result="result" /> >
<SearchItemFaq v-else-if="result.type === 'faq'" :result="result" /> <SearchItem :document="document" />
<SearchItemBlog v-else-if="result.type === 'blog'" :result="result" />
<SearchItemTerm v-else-if="result.type === 'term'" :result="result" />
<SearchItemInclusive v-else-if="result.type === 'inclusive'" :result="result" />
</li> </li>
</ul> </ul>
</section> </section>

View File

@ -6,7 +6,7 @@ import MiniSearch from 'minisearch';
import type { MatchInfo, SearchResult } from 'minisearch'; import type { MatchInfo, SearchResult } from 'minisearch';
import type { Config } from '~/locale/config.ts'; import type { Config } from '~/locale/config.ts';
import { getPosts, type PostMetadata } from '~/server/blog.ts'; import { getPosts } from '~/server/blog.ts';
import { getInclusiveEntries } from '~/server/express/inclusive.ts'; import { getInclusiveEntries } from '~/server/express/inclusive.ts';
import { getNounEntries } from '~/server/express/nouns.ts'; import { getNounEntries } from '~/server/express/nouns.ts';
import { getSourcesEntries } from '~/server/express/sources.ts'; import { getSourcesEntries } from '~/server/express/sources.ts';
@ -17,6 +17,7 @@ import { parsePronouns } from '~/src/buildPronoun.ts';
import { genders, gendersWithNumerus } from '~/src/classes.ts'; import { genders, gendersWithNumerus } from '~/src/classes.ts';
import { clearLinkedText, buildImageUrl } from '~/src/helpers.ts'; import { clearLinkedText, buildImageUrl } from '~/src/helpers.ts';
import parseMarkdown from '~/src/parseMarkdown.ts'; import parseMarkdown from '~/src/parseMarkdown.ts';
import type { SearchDocument } from '~/src/search.ts';
import { Translator } from '~/src/translator.ts'; import { Translator } from '~/src/translator.ts';
import { loadTsv } from '~/src/tsv.ts'; import { loadTsv } from '~/src/tsv.ts';
@ -60,14 +61,16 @@ const highlightMatches = (field: string, terms: string[] | undefined, fragment:
return field.replaceAll(termsRegex, `<mark>$1</mark>`); return field.replaceAll(termsRegex, `<mark>$1</mark>`);
}; };
abstract class SearchIndex<D, R> { abstract class SearchIndex {
documents: D[]; documents: SearchDocument[];
index: MiniSearch<D>; index: MiniSearch<SearchDocument>;
protected constructor(fields: (keyof D)[]) { abstract TYPE: SearchDocument['type'];
constructor() {
this.documents = []; this.documents = [];
this.index = new MiniSearch({ this.index = new MiniSearch({
fields: fields as string[], fields: ['url', 'title', 'titleSmall', 'content'],
storeFields: ['type'], storeFields: ['type'],
}); });
} }
@ -77,36 +80,31 @@ abstract class SearchIndex<D, R> {
this.index.addAll(this.documents); this.index.addAll(this.documents);
} }
abstract getDocuments(config: Config): Promise<D[]>; abstract getDocuments(config: Config): Promise<SearchDocument[]>;
abstract transform(result: SearchResult): R; transform(result: SearchResult): SearchDocument {
const document = this.documents[result.id];
const termsByField = getTermsByField(result.match);
const transformed = structuredClone(document);
transformed.title = highlightMatches(document.title, termsByField.title);
transformed.content = highlightMatches(document.content, termsByField.content, true);
this.transformAdditionalFields(transformed, termsByField);
return transformed;
} }
interface SearchDocumentPronoun { transformAdditionalFields(_transformed: SearchDocument, _termsByField: Record<string, string[]>) {}
id: number;
type: SearchIndexPronoun['TYPE'];
url: string;
short: string;
small: string | undefined;
content: string;
} }
export type SearchResultPronoun = SearchDocumentPronoun; class SearchIndexPronoun extends SearchIndex {
class SearchIndexPronoun extends SearchIndex<SearchDocumentPronoun, SearchResultPronoun> {
TYPE = 'pronoun' as const; TYPE = 'pronoun' as const;
constructor() { async getDocuments(config: Config): Promise<SearchDocument[]> {
super(['url', 'short', 'small', 'content']);
}
async getDocuments(config: Config): Promise<SearchDocumentPronoun[]> {
if (!config.pronouns.enabled) { if (!config.pronouns.enabled) {
return []; return [];
} }
const pronouns = parsePronouns(config, loadTsv(`${rootDir}/data/pronouns/pronouns.tsv`)); const pronouns = parsePronouns(config, loadTsv(`${rootDir}/data/pronouns/pronouns.tsv`));
return Object.values(pronouns).map((pronoun, id): SearchDocumentPronoun => { return Object.values(pronouns).map((pronoun, id): SearchDocument => {
const description = Array.isArray(pronoun.description) const description = Array.isArray(pronoun.description)
? pronoun.description.join() ? pronoun.description.join()
: pronoun.description; : pronoun.description;
@ -120,45 +118,26 @@ class SearchIndexPronoun extends SearchIndex<SearchDocumentPronoun, SearchResult
id, id,
type: this.TYPE, type: this.TYPE,
url: `/${encodeURIComponent(pronoun.canonicalName)}`, url: `/${encodeURIComponent(pronoun.canonicalName)}`,
short: pronoun.name(), title: pronoun.name(),
small: pronoun.smallForm ? pronoun.getMorpheme(pronoun.smallForm) ?? undefined : undefined, titleSmall: pronoun.smallForm ? pronoun.getMorpheme(pronoun.smallForm) ?? undefined : undefined,
content: `${description}: ${history} ${morphemes}`, content: `${description}: ${history} ${morphemes}`,
}; };
}); });
} }
override transform(result: SearchResult): SearchResultPronoun { override transformAdditionalFields(transformed: SearchDocument, termsByField: Record<string, string[]>) {
const document = this.documents[result.id]; transformed.title = `<strong>${transformed.title}</strong>`;
const termsByField = getTermsByField(result.match); if (transformed.titleSmall) {
return { transformed.title += `/<small>${highlightMatches(transformed.titleSmall, termsByField.titleSmall)}</small>`;
id: document.id, delete transformed.titleSmall;
type: document.type, }
url: document.url,
short: highlightMatches(document.short, termsByField.short),
small: document.small ? highlightMatches(document.small, termsByField.small) : undefined,
content: highlightMatches(document.content, termsByField.content, true),
};
} }
} }
interface SearchDocumentNoun { class SearchIndexNoun extends SearchIndex {
id: number;
type: SearchIndexNoun['TYPE'];
url: string;
title: string;
content: string;
}
export type SearchResultNoun = SearchDocumentNoun;
class SearchIndexNoun extends SearchIndex<SearchDocumentNoun, SearchResultNoun> {
TYPE = 'noun' as const; TYPE = 'noun' as const;
constructor() { async getDocuments(config: Config): Promise<SearchDocument[]> {
super(['url', 'title', 'content']);
}
async getDocuments(config: Config): Promise<SearchDocumentNoun[]> {
if (!config.nouns.enabled) { if (!config.nouns.enabled) {
return []; return [];
} }
@ -167,7 +146,7 @@ class SearchIndexNoun extends SearchIndex<SearchDocumentNoun, SearchResultNoun>
const db = useDatabase(); const db = useDatabase();
const nouns = await getNounEntries(db, () => false); const nouns = await getNounEntries(db, () => false);
return nouns.map((noun, id): SearchDocumentNoun => { return nouns.map((noun, id): SearchDocument => {
const firstWords = genders const firstWords = genders
.filter((gender) => noun[gender]) .filter((gender) => noun[gender])
.map((gender) => noun[gender].split('|')[0]); .map((gender) => noun[gender].split('|')[0]);
@ -183,39 +162,12 @@ class SearchIndexNoun extends SearchIndex<SearchDocumentNoun, SearchResultNoun>
}; };
}); });
} }
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 SearchDocumentSource { class SearchIndexSource extends SearchIndex {
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; TYPE = 'source' as const;
constructor() { async getDocuments(config: Config): Promise<SearchDocument[]> {
super(['url', 'title', 'content']);
}
async getDocuments(config: Config): Promise<SearchDocumentSource[]> {
if (!config.sources.enabled) { if (!config.sources.enabled) {
return []; return [];
} }
@ -226,7 +178,7 @@ class SearchIndexSource extends SearchIndex<SearchDocumentSource, SearchResultSo
const db = useDatabase(); const db = useDatabase();
const sources = await getSourcesEntries(db, () => false, undefined); const sources = await getSourcesEntries(db, () => false, undefined);
return sources.map((source, id): SearchDocumentSource => { return sources.map((source, id): SearchDocument => {
let title = ''; let title = '';
if (source.author) { if (source.author) {
title += `${source.author.replace('^', '')} `; title += `${source.author.replace('^', '')} `;
@ -257,44 +209,17 @@ class SearchIndexSource extends SearchIndex<SearchDocumentSource, SearchResultSo
type: this.TYPE, type: this.TYPE,
url: `/${base}?filter=${source.title}`, url: `/${base}?filter=${source.title}`,
title, title,
image, image: image ? { src: image } : undefined,
content, 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 SearchDocumentLink { class SearchIndexLink extends SearchIndex {
id: number;
type: SearchIndexLink['TYPE'];
url: string;
title: string;
content: string;
}
export type SearchResultLink = SearchDocumentLink;
class SearchIndexLink extends SearchIndex<SearchDocumentLink, SearchResultLink> {
TYPE = 'link' as const; TYPE = 'link' as const;
constructor() { async getDocuments(config: Config): Promise<SearchDocument[]> {
super(['url', 'title', 'content']);
}
async getDocuments(config: Config): Promise<SearchDocumentLink[]> {
if (!config.links.enabled) { if (!config.links.enabled) {
return []; return [];
} }
@ -310,38 +235,12 @@ class SearchIndexLink extends SearchIndex<SearchDocumentLink, SearchResultLink>
}; };
}); });
} }
override transform(result: SearchResult): SearchResultLink {
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 SearchDocumentFaq { class SearchIndexFaq extends SearchIndex {
id: number;
type: SearchIndexFaq['TYPE'];
url: string;
title: string;
content: string;
}
export type SearchResultFaq = SearchDocumentFaq;
class SearchIndexFaq extends SearchIndex<SearchDocumentFaq, SearchResultFaq> {
TYPE = 'faq' as const; TYPE = 'faq' as const;
constructor() { async getDocuments(config: Config): Promise<SearchDocument[]> {
super(['url', 'title', 'content']);
}
async getDocuments(config: Config): Promise<SearchDocumentFaq[]> {
if (!config.faq.enabled) { if (!config.faq.enabled) {
return []; return [];
} }
@ -357,42 +256,17 @@ class SearchIndexFaq extends SearchIndex<SearchDocumentFaq, SearchResultFaq> {
}; };
}); });
} }
override transform(result: SearchResult): SearchResultFaq {
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 { class SearchIndexBlog extends SearchIndex {
id: number;
type: SearchIndexBlog['TYPE'];
url: string;
content: string;
}
export type SearchResultBlog = SearchDocumentBlog;
class SearchIndexBlog extends SearchIndex<SearchDocumentBlog, SearchResultBlog> {
TYPE = 'blog' as const; TYPE = 'blog' as const;
constructor() { async getDocuments(config: Config): Promise<SearchDocument[]> {
super(['url', 'title', 'content']);
}
async getDocuments(config: Config): Promise<SearchDocumentBlog[]> {
if (!config.links.enabled || !config.links.blog) { if (!config.links.enabled || !config.links.blog) {
return []; return [];
} }
const documents: SearchDocumentBlog[] = []; const documents: SearchDocument[] = [];
for (const post of (await getPosts())) { for (const post of (await getPosts())) {
const content = await fs.readFile(`${rootDir}/data/blog/${post.slug}.md`, 'utf-8'); const content = await fs.readFile(`${rootDir}/data/blog/${post.slug}.md`, 'utf-8');
// exclude title, date and author from searchable content // exclude title, date and author from searchable content
@ -408,49 +282,19 @@ class SearchIndexBlog extends SearchIndex<SearchDocumentBlog, SearchResultBlog>
title: post.title, title: post.title,
date: post.date, date: post.date,
authors: post.authors, authors: post.authors,
hero: post.hero, image: post.hero,
content: text, content: text,
}); });
} }
} }
return documents; return documents;
} }
override transform(result: SearchResult): SearchResultBlog {
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),
date: document.date,
authors: document.authors,
hero: document.hero,
content: highlightMatches(document.content, termsByField.content, true),
};
}
} }
interface SearchDocumentTerm { class SearchIndexTerm extends SearchIndex {
id: number;
type: SearchIndexTerm['TYPE'];
url: string;
title: string;
image: string | undefined;
content: string;
}
export type SearchResultTerm = SearchDocumentTerm;
class SearchIndexTerm extends SearchIndex<SearchDocumentTerm, SearchResultTerm> {
TYPE = 'term' as const; TYPE = 'term' as const;
constructor() { async getDocuments(config: Config): Promise<SearchDocument[]> {
super(['url', 'title', 'content']);
}
async getDocuments(config: Config): Promise<SearchDocumentTerm[]> {
if (!config.terminology.enabled) { if (!config.terminology.enabled) {
return []; return [];
} }
@ -461,7 +305,7 @@ class SearchIndexTerm extends SearchIndex<SearchDocumentTerm, SearchResultTerm>
const db = useDatabase(); const db = useDatabase();
const terms = await getTermsEntries(db, () => false); const terms = await getTermsEntries(db, () => false);
return terms.map((term, id): SearchDocumentTerm => { return terms.map((term, id): SearchDocument => {
const title = term.term.replaceAll('|', ', '); const title = term.term.replaceAll('|', ', ');
let content = ''; let content = '';
@ -483,44 +327,17 @@ class SearchIndexTerm extends SearchIndex<SearchDocumentTerm, SearchResultTerm>
type: this.TYPE, type: this.TYPE,
url: `/${base}?filter=${term.key}`, url: `/${base}?filter=${term.key}`,
title, title,
image, image: image ? { src: image } : undefined,
content, content,
}; };
}); });
} }
override transform(result: SearchResult): SearchResultTerm {
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 SearchDocumentInclusive { class SearchIndexInclusive extends SearchIndex {
id: number;
type: SearchIndexInclusive['TYPE'];
url: string;
title: string;
content: string;
}
export type SearchResultInclusive = SearchDocumentInclusive;
class SearchIndexInclusive extends SearchIndex<SearchDocumentInclusive, SearchResultInclusive> {
TYPE = 'inclusive' as const; TYPE = 'inclusive' as const;
constructor() { async getDocuments(config: Config): Promise<SearchDocument[]> {
super(['url', 'title', 'content']);
}
async getDocuments(config: Config): Promise<SearchDocumentInclusive[]> {
if (!config.inclusive.enabled) { if (!config.inclusive.enabled) {
return []; return [];
} }
@ -529,7 +346,7 @@ class SearchIndexInclusive extends SearchIndex<SearchDocumentInclusive, SearchRe
const db = useDatabase(); const db = useDatabase();
const inclusiveEntries = await getInclusiveEntries(db, () => false); const inclusiveEntries = await getInclusiveEntries(db, () => false);
return inclusiveEntries.map((inclusiveEntry, id): SearchDocumentInclusive => { return inclusiveEntries.map((inclusiveEntry, id): SearchDocument => {
const insteadOf = inclusiveEntry.insteadOf.split('|'); const insteadOf = inclusiveEntry.insteadOf.split('|');
const say = inclusiveEntry.say?.split('|') ?? []; const say = inclusiveEntry.say?.split('|') ?? [];
let content = `${translator.translate('inclusive.insteadOf')}: ${insteadOf.join(', ')}` + let content = `${translator.translate('inclusive.insteadOf')}: ${insteadOf.join(', ')}` +
@ -548,18 +365,6 @@ class SearchIndexInclusive extends SearchIndex<SearchDocumentInclusive, SearchRe
}; };
}); });
} }
override transform(result: SearchResult): SearchResultInclusive {
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),
};
}
} }
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
@ -587,6 +392,6 @@ export default defineEventHandler(async (event) => {
return resultB.score - resultA.score; return resultB.score - resultA.score;
}) })
.map((result) => { .map((result) => {
return indices[result.type as keyof typeof indices].transform(result); return indices[result.type].transform(result);
}); });
}); });

17
src/search.ts Normal file
View File

@ -0,0 +1,17 @@
interface SearchDocumentImage {
src: string;
alt?: string;
class?: string;
}
export interface SearchDocument {
id: number;
type: 'pronoun' | 'noun' | 'source' | 'link' | 'faq' | 'blog' | 'term' | 'inclusive';
url: string;
title: string;
titleSmall?: string | undefined;
image?: SearchDocumentImage | undefined;
content: string;
date?: string;
authors?: string[];
}