mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-24 05:05:20 -04:00
(search) highlight found search terms
This commit is contained in:
parent
e8f5f22ba7
commit
9d15cb1672
@ -31,7 +31,7 @@ const searchInput = useTemplateRef('searchInput');
|
||||
</section>
|
||||
<section>
|
||||
<ul class="list-group">
|
||||
<li v-for="result of results.data.value" :key="result.id" class="list-group-item">
|
||||
<li v-for="result of results.data.value" :key="result.url" class="list-group-item">
|
||||
<nuxt-link :to="result.url" class="d-flex text-dark">
|
||||
<div class="col-auto pt-1 pe-3">
|
||||
<Icon v="pen-nib" class="h3" />
|
||||
@ -46,7 +46,8 @@ const searchInput = useTemplateRef('searchInput');
|
||||
>
|
||||
</div>
|
||||
<div class="col ps-2">
|
||||
<Spelling class="h4" :text="result.title" />
|
||||
<Spelling class="d-block h4" :text="result.title" />
|
||||
<Spelling :text="result.fragment" />
|
||||
<ul class="list-inline mb-0 small">
|
||||
<li class="list-inline-item small">
|
||||
<Icon v="calendar" />
|
||||
|
@ -3,6 +3,7 @@ import fs from 'node:fs/promises';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import marked from 'marked';
|
||||
import MiniSearch from 'minisearch';
|
||||
import type { MatchInfo } from 'minisearch';
|
||||
|
||||
import { getPosts, type PostMetadata } from '~/server/blog.ts';
|
||||
import { loadSuml, loadSumlFromBase } from '~/server/loader.ts';
|
||||
@ -21,6 +22,41 @@ interface SearchDocumentPost extends PostMetadata {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const getTermsByField = (matches: MatchInfo): Record<string, string[]> => {
|
||||
const termsByField: Record<string, string[]> = {};
|
||||
for (const [term, fields] of Object.entries(matches)) {
|
||||
if (term.length === 1) {
|
||||
continue;
|
||||
}
|
||||
for (const field of fields) {
|
||||
if (!Object.hasOwn(termsByField, field)) {
|
||||
termsByField[field] = [];
|
||||
}
|
||||
termsByField[field].push(term);
|
||||
}
|
||||
}
|
||||
return termsByField;
|
||||
};
|
||||
|
||||
const FRAGMENT_MAX_WORDCOUNT = 24;
|
||||
|
||||
const highlightMatches = (field: string, terms: string[] | undefined, fragment: boolean = false): string => {
|
||||
const termsRegex = terms && terms.length > 0 ? new RegExp(`\\b(${terms.join('|')})\\b`, 'ig') : undefined;
|
||||
|
||||
if (fragment) {
|
||||
const words = field.split(' ');
|
||||
const firstMatch = termsRegex !== undefined ? words.findIndex((word) => word.match(termsRegex)) : 0;
|
||||
const start = Math.max(Math.min(firstMatch - 2, words.length - FRAGMENT_MAX_WORDCOUNT), 0);
|
||||
const end = Math.min(start + FRAGMENT_MAX_WORDCOUNT, words.length);
|
||||
field = `${start > 0 ? '[…] ' : ''}${words.slice(start, end).join(' ')}${end < words.length ? ' […]' : ''}`;
|
||||
}
|
||||
|
||||
if (termsRegex === undefined) {
|
||||
return field;
|
||||
}
|
||||
return field.replaceAll(termsRegex, `<mark>$1</mark>`);
|
||||
};
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const index = new MiniSearch<SearchDocumentPost>({
|
||||
fields: ['url', 'title', 'content'],
|
||||
@ -29,7 +65,9 @@ export default defineEventHandler(async (event) => {
|
||||
const posts: SearchDocumentPost[] = [];
|
||||
for (const post of (await getPosts())) {
|
||||
const content = await fs.readFile(`${rootDir}/data/blog/${post.slug}.md`, 'utf-8');
|
||||
const markdown = marked(content);
|
||||
// exclude title, date and author from searchable content
|
||||
const trimmed = content.replace(/^(.+\n+){2}/, '');
|
||||
const markdown = marked(trimmed);
|
||||
const parsed = await parseMarkdown(markdown, translator);
|
||||
const text = JSDOM.fragment(parsed.content ?? '').textContent;
|
||||
if (text !== null && config.links.enabled && config.links.blog) {
|
||||
@ -50,6 +88,16 @@ export default defineEventHandler(async (event) => {
|
||||
const text = query.text as string;
|
||||
const results = index.search(text, { prefix: true, fuzzy: 1 });
|
||||
return results.map((result) => {
|
||||
return posts[result.id];
|
||||
const post = posts[result.id];
|
||||
const termsByField = getTermsByField(result.match);
|
||||
return {
|
||||
url: post.url,
|
||||
title: highlightMatches(post.title, termsByField.title),
|
||||
date: post.date,
|
||||
authors: post.authors,
|
||||
hero: post.hero,
|
||||
fragment: highlightMatches(post.content, termsByField.content, true),
|
||||
termsByField,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user