Word match

This commit is contained in:
Manav Rathi
2024-09-18 16:32:30 +05:30
parent c80a066518
commit b254bb5b0c
4 changed files with 29 additions and 27 deletions

View File

@@ -483,6 +483,7 @@ const labelForOption = (option: SearchOption) => {
case "date":
return t("date");
case "location":
return t("location");

View File

@@ -160,11 +160,11 @@ const localizedSearchData = () =>
locale: i18n.language,
holidays: holidays().map((h) => ({
...h,
lowercasedName: h.label.toLowerCase(),
searchTerms: h.label.toLowerCase().split(" "),
})),
labelledFileTypes: labelledFileTypes().map((t) => ({
...t,
lowercasedName: t.label.toLowerCase(),
searchTerms: t.label.toLowerCase().split(" "),
})),
});

View File

@@ -63,17 +63,17 @@ export interface LabelledFileType {
}
/**
* An annotated version of {@link T} that includes its searchable "lowercased"
* label or name.
* An annotated version of {@link T} that includes a list of lowercased words
* that it should match.
*
* Precomputing these lowercased values saves us from doing the lowercasing
* during the search itself.
*/
export type Searchable<T> = T & {
/**
* The name or label of T, lowercased.
* The name or label of T, split into words and lowercased.
*/
lowercasedName: string;
searchTerms: string[];
};
/**

View File

@@ -53,13 +53,13 @@ export class SearchWorker {
.then((ts) => {
this.locationTags = ts.map((t) => ({
...t,
lowercasedName: t.name.toLowerCase(),
searchTerms: t.name.toLowerCase().split(" "),
}));
}),
fetchCities().then((cs) => {
this.cities = cs.map((c) => ({
...c,
lowercasedName: c.name.toLowerCase(),
searchTerms: c.name.toLowerCase().split(" "),
}));
}),
]);
@@ -77,7 +77,10 @@ export class SearchWorker {
*/
setPeople(people: Person[]) {
this.searchablePeople = people.map((person) => {
return { ...person, lowercasedName: person.name.toLowerCase() };
return {
...person,
searchTerms: person.name.toLowerCase().split(" "),
};
});
}
@@ -165,9 +168,17 @@ const fileTypeSuggestions = (
labelledFileTypes: Searchable<LabelledFileType>[],
): SearchSuggestion[] =>
labelledFileTypes
.filter(({ lowercasedName }) => lowercasedName.startsWith(s))
.filter(searchableMatch(s))
.map(({ fileType, label }) => ({ type: "fileType", fileType, label }));
/**
* A helper function to directly pass to filters on Searchable<T>[].
*/
const searchableMatch =
(s: string) =>
({ searchTerms }: { searchTerms: string[] }) =>
searchTerms.some((t) => t.startsWith(s));
const fileNameSuggestion = (
s: string,
searchString: string,
@@ -211,7 +222,7 @@ const peopleSuggestions = (
searchablePeople: Searchable<Person>[],
): SearchSuggestion[] =>
searchablePeople
.filter(({ lowercasedName }) => lowercasedName.startsWith(s))
.filter(searchableMatch(s))
.map((person) => ({ type: "person", person, label: person.name }));
const dateSuggestions = (
@@ -247,7 +258,7 @@ const parseDateComponents = (
[
parseChrono(s, locale),
parseYearComponents(s),
holidays.filter(searchableIncludes(s)),
holidays.filter(searchableMatch(s)),
].flat();
const parseChrono = (
@@ -299,14 +310,6 @@ const parseYearComponents = (s: string): LabelledSearchDateComponents[] => {
return [];
};
/**
* A helper function to directly pass to filters on Searchable<T>[].
*/
const searchableIncludes =
(s: string) =>
({ lowercasedName }: { lowercasedName: string }) =>
lowercasedName.includes(s);
/**
* Zod schema describing world_cities.json.
*
@@ -335,17 +338,15 @@ const locationSuggestions = (
locationTags: Searchable<LocationTag>[],
cities: Searchable<City>[],
): SearchSuggestion[] => {
const matchingLocationTags = locationTags.filter(searchableIncludes(s));
const matchingLocationTags = locationTags.filter(searchableMatch(s));
const matchingLocationTagLNames = new Set(
matchingLocationTags.map((t) => t.lowercasedName),
matchingLocationTags.map((t) => t.searchTerms),
);
const matchingCities = cities.filter(
(c) =>
c.lowercasedName.startsWith(s) &&
!matchingLocationTagLNames.has(c.lowercasedName),
);
const matchingCities = cities
.filter(searchableMatch(s))
.filter((c) => !matchingLocationTagLNames.has(c.searchTerms));
return [
matchingLocationTags.map(