mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-08-02 17:11:48 +00:00
Refactoring: moved some source files into subdirectories
This commit is contained in:
105
src/menu/search_index.ts
Normal file
105
src/menu/search_index.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import lunr from 'lunr';
|
||||
import naturalSort from 'javascript-natural-sort';
|
||||
import {idToFamMap, idToIndiMap} from '../util/gedcom_util';
|
||||
import {JsonFam, JsonGedcomData, JsonIndi} from 'topola';
|
||||
|
||||
const MAX_RESULTS = 8;
|
||||
|
||||
export interface SearchResult {
|
||||
id: string;
|
||||
indi: JsonIndi;
|
||||
}
|
||||
|
||||
export interface SearchIndex {
|
||||
search(input: string): SearchResult[];
|
||||
}
|
||||
|
||||
/** Removes accents from letters, e.g. ó->o, ę->e. */
|
||||
function normalize(input: string) {
|
||||
return input
|
||||
.toLocaleLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/\u0142/g, 'l'); // Special case: ł is not affected by NFD.
|
||||
}
|
||||
|
||||
/** Comparator to sort by score first, then by id. */
|
||||
function compare(a: lunr.Index.Result, b: lunr.Index.Result) {
|
||||
if (a.score !== b.score) {
|
||||
return b.score - a.score;
|
||||
}
|
||||
return naturalSort(a.ref, b.ref);
|
||||
}
|
||||
|
||||
/** Returns all last names of all husbands as a space-separated string. */
|
||||
function getHusbandLastName(
|
||||
indi: JsonIndi,
|
||||
indiMap: Map<String, JsonIndi>,
|
||||
famMap: Map<string, JsonFam>,
|
||||
): string {
|
||||
return (indi.fams || [])
|
||||
.map((famId) => famMap.get(famId))
|
||||
.map((fam) => fam && fam.husb)
|
||||
.map((husbId) => husbId && indiMap.get(husbId))
|
||||
.map((husband) => husband && husband.lastName)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
class LunrSearchIndex implements SearchIndex {
|
||||
private index: lunr.Index | undefined;
|
||||
private indiMap: Map<string, JsonIndi>;
|
||||
private famMap: Map<string, JsonFam>;
|
||||
|
||||
constructor(data: JsonGedcomData) {
|
||||
this.indiMap = idToIndiMap(data);
|
||||
this.famMap = idToFamMap(data);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
const self = this;
|
||||
this.index = lunr(function() {
|
||||
this.ref('id');
|
||||
this.field('id');
|
||||
this.field('name', {boost: 10});
|
||||
this.field('normalizedName', {boost: 8});
|
||||
this.field('spouseLastName', {boost: 2});
|
||||
this.field('normalizedSpouseLastName', {boost: 2});
|
||||
|
||||
self.indiMap.forEach((indi) => {
|
||||
const name = [indi.firstName, indi.lastName].join(' ');
|
||||
const spouseLastName = getHusbandLastName(
|
||||
indi,
|
||||
self.indiMap,
|
||||
self.famMap,
|
||||
);
|
||||
this.add({
|
||||
id: indi.id,
|
||||
name,
|
||||
normalizedName: normalize(name),
|
||||
spouseLastName,
|
||||
normalizedSpouseLastName: normalize(spouseLastName),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public search(input: string): SearchResult[] {
|
||||
const query = input
|
||||
.split(' ')
|
||||
.filter((s) => !!s)
|
||||
.map((s) => `+${s}*`)
|
||||
.join(' ');
|
||||
const results = this.index!.search(query);
|
||||
return results
|
||||
.sort(compare)
|
||||
.slice(0, MAX_RESULTS)
|
||||
.map((result) => ({id: result.ref, indi: this.indiMap.get(result.ref)!}));
|
||||
}
|
||||
}
|
||||
|
||||
/** Builds a search index from data. */
|
||||
export function buildSearchIndex(data: JsonGedcomData): SearchIndex {
|
||||
const index = new LunrSearchIndex(data);
|
||||
index.initialize();
|
||||
return index;
|
||||
}
|
||||
Reference in New Issue
Block a user