Index husband's last name for better search results

This commit is contained in:
Przemek Wiech
2019-11-29 23:35:49 +01:00
parent 1bf315ebdf
commit 07bcafc7dc
2 changed files with 57 additions and 14 deletions

View File

@@ -1,6 +1,6 @@
import naturalSort from 'javascript-natural-sort'; import naturalSort from 'javascript-natural-sort';
import lunr from 'lunr'; import lunr from 'lunr';
import {GedcomData} from './gedcom_util'; import {GedcomData, pointerToId} from './gedcom_util';
import {GedcomEntry} from 'parse-gedcom'; import {GedcomEntry} from 'parse-gedcom';
const MAX_RESULTS = 8; const MAX_RESULTS = 8;
@@ -26,27 +26,67 @@ function normalize(input: string) {
/** Comparator to sort by score first, then by id. */ /** Comparator to sort by score first, then by id. */
function compare(a: lunr.Index.Result, b: lunr.Index.Result) { function compare(a: lunr.Index.Result, b: lunr.Index.Result) {
if (a.score !== b.score) { if (a.score !== b.score) {
return a.score - b.score; return b.score - a.score;
} }
return naturalSort(a.ref, b.ref); return naturalSort(a.ref, b.ref);
} }
class LunrSearchIndex implements SearchIndex { /** Returns all last names of all husbands as a space-separated string. */
private index: lunr.Index; function getHusbandLastName(indi: GedcomEntry, gedcom: GedcomData): string {
return indi.tree
.filter((entry) => entry.tag === 'FAMS')
.map((entry) => gedcom.fams[pointerToId(entry.data)])
.filter((entry) => !!entry)
.map((entry) => {
const husband = entry.tree.find((entry) => entry.tag === 'HUSB');
const husbandId = husband && pointerToId(husband.data);
return (
husbandId &&
husbandId !== pointerToId(indi.pointer) &&
gedcom.indis[husbandId]
);
})
.filter((entry) => !!entry)
.flatMap((husband) =>
(husband as GedcomEntry).tree
.filter((entry) => entry.tag === 'NAME')
.map((entry) => {
const names = entry.data.split('/');
return names.length >= 2 ? names[1] : '';
}),
)
.join(' ');
}
public constructor(private gedcom: GedcomData) { class LunrSearchIndex implements SearchIndex {
private index: lunr.Index | undefined;
constructor(private gedcom: GedcomData) {}
initialize() {
const self = this;
this.index = lunr(function() { this.index = lunr(function() {
this.ref('id'); this.ref('id');
this.field('id'); this.field('id');
this.field('name'); this.field('name', {boost: 10});
this.field('normalizedName'); this.field('normalizedName', {boost: 8});
this.field('spouseLastName', {boost: 2});
this.field('normalizedSpouseLastName', {boost: 2});
for (let id in gedcom.indis) { for (let id in self.gedcom.indis) {
const name = gedcom.indis[id].tree const indi = self.gedcom.indis[id];
const name = indi.tree
.filter((entry) => entry.tag === 'NAME') .filter((entry) => entry.tag === 'NAME')
.map((entry) => entry.data) .map((entry) => entry.data)
.join(' '); .join(' ');
this.add({id, name, normalizedName: normalize(name)}); const spouseLastName = getHusbandLastName(indi, self.gedcom);
this.add({
id,
name,
normalizedName: normalize(name),
spouseLastName,
normalizedSpouseLastName: normalize(spouseLastName),
});
} }
}); });
} }
@@ -57,7 +97,7 @@ class LunrSearchIndex implements SearchIndex {
.filter((s) => !!s) .filter((s) => !!s)
.map((s) => `+${s}*`) .map((s) => `+${s}*`)
.join(' '); .join(' ');
const results = this.index.search(query); const results = this.index!.search(query);
return results return results
.sort(compare) .sort(compare)
.slice(0, MAX_RESULTS) .slice(0, MAX_RESULTS)
@@ -67,5 +107,7 @@ class LunrSearchIndex implements SearchIndex {
/** Builds a search index from data. */ /** Builds a search index from data. */
export function buildSearchIndex(gedcom: GedcomData): SearchIndex { export function buildSearchIndex(gedcom: GedcomData): SearchIndex {
return new LunrSearchIndex(gedcom); const index = new LunrSearchIndex(gedcom);
index.initialize();
return index;
} }

View File

@@ -3,7 +3,7 @@ import * as React from 'react';
import debounce from 'debounce'; import debounce from 'debounce';
import md5 from 'md5'; import md5 from 'md5';
import {analyticsEvent} from './analytics'; import {analyticsEvent} from './analytics';
import {buildSearchIndex, SearchIndex, SearchResult} from './search_index'; import {buildSearchIndex, SearchIndex} from './search_index';
import {displaySearchResult} from './search_util'; import {displaySearchResult} from './search_util';
import {FormattedMessage, intlShape} from 'react-intl'; import {FormattedMessage, intlShape} from 'react-intl';
import {GedcomData} from './gedcom_util'; import {GedcomData} from './gedcom_util';
@@ -21,13 +21,14 @@ import {
Dropdown, Dropdown,
Search, Search,
SearchProps, SearchProps,
SearchResultProps,
} from 'semantic-ui-react'; } from 'semantic-ui-react';
/** Menus and dialogs state. */ /** Menus and dialogs state. */
interface State { interface State {
loadUrlDialogOpen: boolean; loadUrlDialogOpen: boolean;
url?: string; url?: string;
searchResults: SearchResult[]; searchResults: SearchResultProps[];
} }
interface EventHandlers { interface EventHandlers {